diff --git a/.gitignore b/.gitignore index a0d91fd7c..481b36fb7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,3 @@ # /lib/ /lib/java /lib/java-contrib - diff --git a/src/org/sosy_lab/common/collect/AbstractImmutableDeque.java b/src/org/sosy_lab/common/collect/AbstractImmutableDeque.java new file mode 100644 index 000000000..06b8f91e7 --- /dev/null +++ b/src/org/sosy_lab/common/collect/AbstractImmutableDeque.java @@ -0,0 +1,259 @@ +// This file is part of SoSy-Lab Common, +// a library of useful utilities: +// https://github.com/sosy-lab/java-common-lib +// +// SPDX-FileCopyrightText: 2026 Dirk Beyer +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.common.collect; + +import com.google.errorprone.annotations.DoNotCall; +import com.google.errorprone.annotations.Immutable; +import java.util.Collection; +import java.util.Deque; + +@Immutable(containerOf = "T") +public abstract class AbstractImmutableDeque implements Deque { + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean add(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final void addFirst(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final void addLast(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean offer(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean offerFirst(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean offerLast(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T poll() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T pollFirst() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T pollLast() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T pop() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final void push(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T remove() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T removeFirst() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean removeFirstOccurrence(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final T removeLast() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean removeLastOccurrence(Object o) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final void clear() { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + public final boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/org/sosy_lab/common/collect/PersistentBalancingDoubleListDeque.java b/src/org/sosy_lab/common/collect/PersistentBalancingDoubleListDeque.java new file mode 100644 index 000000000..0e9999fca --- /dev/null +++ b/src/org/sosy_lab/common/collect/PersistentBalancingDoubleListDeque.java @@ -0,0 +1,522 @@ +// This file is part of SoSy-Lab Common, +// a library of useful utilities: +// https://github.com/sosy-lab/java-common-lib +// +// SPDX-FileCopyrightText: 2026 Dirk Beyer +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.common.collect; + +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.Var; +import java.util.Iterator; +import java.util.NoSuchElementException; +import javax.annotation.Nullable; + +/** + * A persistent implementation of a deque on the basis of {@link PersistentLinkedList}. + * + *

To avoid O(n) runtime complexity when accessing the bottom of the deque, two separate {@link + * PersistentLinkedList}s are used. {@code top} represents the top part of the deque, while {@code + * bottom} forms the lower part of the deque. If one were to reverse {@code bottom} and then add it + * to the bottom of {@code top}, one would receive one list representing the whole deque in correct + * order. + * + *

It provides operations to show the top- and bottom-most elements of the deque, as well as ones + * to remove them or add new items to the deque in either places. In most cases, these will complete + * in O(1). Occasionally, these operations will require more time, as the deque might need to be + * rebalanced (i.e. when one of the lists becomes empty, the other list is split up into top and + * bottom to further guarantee access to both ends of the deque). + * + *

Currently, it is only possible to create an empty deque and then add new elements one at a + * time. + * + * @param type of elements to be stored in deque + */ +@Immutable(containerOf = "T") +public final class PersistentBalancingDoubleListDeque extends AbstractImmutableDeque + implements PersistentDeque { + final PersistentLinkedList top; + final PersistentLinkedList bottom; + + public PersistentBalancingDoubleListDeque() { + top = PersistentLinkedList.of(); + bottom = PersistentLinkedList.of(); + } + + private PersistentBalancingDoubleListDeque( + PersistentLinkedList top, PersistentLinkedList bottom) { + this.top = top; + this.bottom = bottom; + } + + /** + * Creates a new deque instance that is empty. + * + * @return new, empty deque instance + */ + @SuppressWarnings({"unchecked"}) + @Override + public PersistentBalancingDoubleListDeque empty() { + return of(); + } + + /** + * Creates a new deque instance that is empty. + * + * @return new, empty deque instance + */ + @SuppressWarnings("rawtypes") + public static PersistentBalancingDoubleListDeque of() { + return new PersistentBalancingDoubleListDeque(); + } + + /** + * Checks both sublists and returns true if both are empty, false if at least one is not. + * + * @return true if {@code top} and {@code bottom} are both empty, false if at least one is not + */ + @Override + public boolean isEmpty() { + return top.isEmpty() && bottom.isEmpty(); + } + + /** + * Retrieves element at top of deque or returns null in the case of an empty deque. + * + * @return element at top of deque; null if deque empty + */ + public T peek() { + return peekFirst(); + } + + /** + * Returns element at the top of the deque. + * + * @return element at top of deque; null if deque empty + */ + @Nullable + @Override + public T peekFirst() { + // If deque contains only one element, one of the lists will be empty. Calling head() on an + // empty list throws an exception, which is why we check first with isEmpty(). Further, the one + // element in + // the non-empty list should be returned, as it is both head and tail of the deque. + + if (!top.isEmpty()) { + return top.head(); + } else if (!bottom.isEmpty()) { + return bottom.head(); + } + + return null; + } + + /** + * Returns element at the bottom of the deque. + * + * @return element at bottom of deque; null if deque empty + */ + @Nullable + @Override + public T peekLast() { + // If deque contains only one element, one of the lists will be empty. Calling head() on an + // empty list throws an exception, which is why we check first with isEmpty(). Further, the one + // element in + // the non-empty list should be returned, as it is both head and tail of the deque. + + if (!bottom.isEmpty()) { + return bottom.head(); + } else if (!top.isEmpty()) { + return top.head(); + } + + return null; + } + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * + * @return copy of existing deque extended by new element in last place + */ + @Override + public PersistentBalancingDoubleListDeque copyAndAdd(T value) { + return copyAndAddLast(value); + } + + /** + * Places new element on top of the deque. + * + * @param value element to be added to deque + * @return deque instance with new element added on top + */ + @Override + public PersistentBalancingDoubleListDeque copyAndAddFirst(T value) { + return new PersistentBalancingDoubleListDeque<>(top.with(value), bottom); + } + + /** + * Places new element at the bottom of the deque. + * + * @param value element to be added to deque + * @return deque instance with new element added at the bottom + */ + @Override + public PersistentBalancingDoubleListDeque copyAndAddLast(T value) { + return new PersistentBalancingDoubleListDeque<>(top, bottom.with(value)); + } + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * + * @return copy of existing deque extended by new element in last place + */ + @Override + public PersistentBalancingDoubleListDeque copyAndOffer(T value) { + return copyAndOfferLast(value); + } + + /** + * Returns a copy of the deque to which the provided element has been added in first place. + * + * @return copy of existing deque extended by new element in first place + */ + @Override + public PersistentBalancingDoubleListDeque copyAndOfferFirst(T value) { + return new PersistentBalancingDoubleListDeque<>(top.with(value), bottom); + } + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * + * @return copy of existing deque extended by new element in last place + */ + @Override + public PersistentBalancingDoubleListDeque copyAndOfferLast(T value) { + return new PersistentBalancingDoubleListDeque<>(top, bottom.with(value)); + } + + /** + * Returns a copy of this deque from which the first element has been removed. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the first element has been removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndRemove() { + return copyAndRemoveFirst(); + } + + /** + * Returns a copy of this deque from which the first element has been removed. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the first element has been removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndRemoveFirst() { + if (this.isEmpty()) { + throw new NoSuchElementException("Deque is empty!"); + } + // top should only ever be empty if only one element in bottom or deque completely empty + if (top.isEmpty()) { + return new PersistentBalancingDoubleListDeque<>(top, bottom.tail()).rebalanceDeque(); + } + return new PersistentBalancingDoubleListDeque<>(top.tail(), bottom).rebalanceDeque(); + } + + /** + * Returns a copy of this deque from which the last element has been removed. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the last element has been removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndRemoveLast() { + if (this.isEmpty()) { + throw new NoSuchElementException("Deque is empty!"); + } + // bottom should only ever be empty if only one element in top or deque completely empty + if (bottom.isEmpty()) { + return new PersistentBalancingDoubleListDeque<>(top.tail(), bottom).rebalanceDeque(); + } + return new PersistentBalancingDoubleListDeque<>(top, bottom.tail()).rebalanceDeque(); + } + + /** + * Returns a copy of this deque from which the first element has been removed or null if the deque + * is empty. + * + * @return copy of this deque from which the first element has been removed, null if deque empty + */ + @Override + @Nullable + public PersistentBalancingDoubleListDeque copyAndPoll() { + return copyAndPollFirst(); + } + + /** + * Returns a copy of this deque from which the first element has been removed or null if the deque + * is empty. + * + * @return copy of this deque from which the first element has been removed, null if deque empty + */ + @Override + @Nullable + public PersistentBalancingDoubleListDeque copyAndPollFirst() { + if (isEmpty()) { + return null; + } + // top should only ever be empty if only one element in bottom or deque completely empty + if (top.isEmpty()) { + return new PersistentBalancingDoubleListDeque<>(top, bottom.tail()).rebalanceDeque(); + } + return new PersistentBalancingDoubleListDeque<>(top.tail(), bottom).rebalanceDeque(); + } + + /** + * Returns a copy of this deque from which the last element has been removed or null if the deque + * is empty. + * + * @return copy of this deque from which the last element has been removed, null if deque empty + */ + @Override + @Nullable + public PersistentBalancingDoubleListDeque copyAndPollLast() { + if (isEmpty()) { + return null; + } + // bottom should only ever be empty if only one element in top or deque completely empty + if (bottom.isEmpty()) { + return new PersistentBalancingDoubleListDeque<>(top.tail(), bottom).rebalanceDeque(); + } + return new PersistentBalancingDoubleListDeque<>(top, bottom.tail()).rebalanceDeque(); + } + + /** + * Retrieves element at top of deque. + * + * @throws NoSuchElementException if deque empty + * @return element at top of deque + */ + @Override + public T getFirst() { + if (this.isEmpty()) { + throw new NoSuchElementException("Deque is empty!"); + } + + return peekFirst(); + } + + /** + * Retrieves element at bottom of deque. + * + * @throws NoSuchElementException if deque empty + * @return element at bottom of deque + */ + @Override + public T getLast() { + if (this.isEmpty()) { + throw new NoSuchElementException("Deque is empty!"); + } + + return peekLast(); + } + + /** + * Returns a copy of this deque from which the first occurrence of the provided element has been + * removed. + * + * @return copy of this deque from which the first occurrence of the provided element has been + * removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndRemove(T value) { + return copyAndRemoveFirstOccurrence(value); + } + + /** + * Returns a copy of this deque from which the first occurrence of the provided element has been + * removed. + * + * @return copy of this deque from which the first occurrence of the provided element has been + * removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndRemoveFirstOccurrence(T value) { + // afaik without() will remove the first occurrence of the object + if (top.contains(value)) { + return new PersistentBalancingDoubleListDeque<>(top.without(value), bottom); + } else { + return new PersistentBalancingDoubleListDeque<>(top, bottom.without(value)); + } + } + + /** + * Returns a copy of this deque from which the last occurrence of the provided element has been + * removed. + * + * @return copy of this deque from which the last occurrence of the provided element has been + * removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndRemoveLastOccurrence(T value) { + // reverse deque; remove first occurrence; reverse again to have original order without object + PersistentBalancingDoubleListDeque reversed = this.reversed(); + reversed = reversed.copyAndRemoveFirstOccurrence(value); + + return reversed.reversed(); + } + + private PersistentBalancingDoubleListDeque reversed() { + return new PersistentBalancingDoubleListDeque<>(bottom, top); + } + + /** + * Retrieves element at top of deque. + * + * @throws NoSuchElementException if deque empty + * @return element at top of deque + */ + public T element() { + return getFirst(); + } + + /** + * Returns a copy of this deque to which the provided object has been pushed onto the stack + * represented by this deque. + * + * @throws NullPointerException if provided object is null + * @return copy of this deque to which the provided element has been added in first place + */ + @Override + public PersistentBalancingDoubleListDeque copyAndPush(T value) { + return copyAndAddFirst(value); + } + + /** + * Returns a copy of this deque from which the first element has been removed. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the first element has been removed + */ + @Override + public PersistentBalancingDoubleListDeque copyAndPop() { + return copyAndRemoveFirst(); + } + + /** + * Searches the deque for the provided object and returns true if it is found in the deque, false + * if not. + * + * @param o object to be searched for in the deque + * @return true if o in deque, false if not + */ + public boolean contains(Object o) { + return (top.contains(o) | bottom.contains(o)); + } + + /** + * Calculates the no. of elements in the deque. + * + * @return no. of elements in deque + */ + public int size() { + return top.size() + bottom.size(); + } + + /** + * Provides an iterator over all objects in the deque in proper sequence (from first to last). + * + * @return iterator over all objects in deque in proper sequence + */ + public Iterator iterator() { + return new DequeIterator(top, bottom.reversed()); + } + + /** + * Provides an iterator over all objects in the deque in reverse sequential order (from last to + * first). + * + * @return iterator over all objects in deque in reverse sequential order + */ + public Iterator descendingIterator() { + return new DequeIterator(bottom, top.reversed()); + } + + /** + * Due to the two-list structure of this deque, adding and removing elements can quickly lead to + * one of the lists being empty while the other still contains multiple elements. To prevent this, + * this method is called after each remove or insert operation to even out the two lists if + * necessary. If one list is empty while the other contains at least two elements, the elements in + * the latter will be redistributed so both lists are (almost) the same size while still + * maintaining their original order (this is what split() does). + * + * @return deque with the same elements in the same order, but evenly distributed between both the + * top and bottom list + */ + private PersistentBalancingDoubleListDeque rebalanceDeque() { + boolean topEmpty = top.isEmpty(); + boolean bottomEmpty = bottom.isEmpty(); + + if (topEmpty && bottomEmpty) { + return this; + } else if (topEmpty && !bottomEmpty) { + return split(bottom.reversed()); + } else if (!topEmpty && bottomEmpty) { + return split(top); + } + + return this; + } + + private PersistentBalancingDoubleListDeque split(PersistentLinkedList list) { + int size = list.size(); + int halfSize = size / 2; + + if (size <= 0) { + throw new IllegalArgumentException("Cannot split empty list!"); + } else if (size == 1) { + return this; + } + + @Var PersistentLinkedList newTop = PersistentLinkedList.of(); + @Var PersistentLinkedList newBottom = PersistentLinkedList.of(); + Iterator iterator = list.iterator(); + + for (int i = 0; i < size; i++) { + T element = iterator.next(); + if (i < halfSize) { + newTop = newTop.with(element); + } else { + newBottom = newBottom.with(element); + } + } + newTop = newTop.reversed(); + return new PersistentBalancingDoubleListDeque<>(newTop, newBottom); + } + + private class DequeIterator implements Iterator { + Iterator topIterator; + Iterator bottomIterator; + + protected DequeIterator(PersistentLinkedList top, PersistentLinkedList bottom) { + topIterator = top.iterator(); + bottomIterator = bottom.iterator(); + } + + @Override + public boolean hasNext() { + return (topIterator.hasNext() || bottomIterator.hasNext()); + } + + @Override + public T next() { + if (topIterator.hasNext()) { + return topIterator.next(); + } + return bottomIterator.next(); + } + } +} diff --git a/src/org/sosy_lab/common/collect/PersistentDeque.java b/src/org/sosy_lab/common/collect/PersistentDeque.java new file mode 100644 index 000000000..ef22ff124 --- /dev/null +++ b/src/org/sosy_lab/common/collect/PersistentDeque.java @@ -0,0 +1,442 @@ +// This file is part of SoSy-Lab Common, +// a library of useful utilities: +// https://github.com/sosy-lab/java-common-lib +// +// SPDX-FileCopyrightText: 2026 Dirk Beyer +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.common.collect; + +import com.google.errorprone.annotations.CheckReturnValue; +import com.google.errorprone.annotations.DoNotCall; +import com.google.errorprone.annotations.Immutable; +import java.util.Collection; +import java.util.Deque; +import java.util.NoSuchElementException; +import javax.annotation.Nullable; + +/** + * Interface for persistent deques. A persistent data structure is immutable, but provides cheap * + * copy-and-write operations. Thus, all write operations will not modify the current instance, but + * return a new instance instead. + * + * @param type of elements stored in deque + */ +@Immutable(containerOf = "T") +public interface PersistentDeque extends Deque { + + /** + * Creates a new deque instance that is empty. + * + * @return new, empty deque instance + */ + PersistentDeque empty(); + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * Replacement for {@link #add(Object)}. + * + * @return copy of existing deque extended by new element in last place + */ + @CheckReturnValue + PersistentDeque copyAndAdd(T t); + + /** + * Returns a copy of the deque to which the provided element has been added in first place. + * Replacement for {@link #addFirst(Object)}. + * + * @return copy of existing deque extended by new element in first place + */ + @CheckReturnValue + PersistentDeque copyAndAddFirst(T t); + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * Replacement for {@link #addLast(Object)}. + * + * @return copy of existing deque extended by new element in last place + */ + @CheckReturnValue + PersistentDeque copyAndAddLast(T t); + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * Replacement for {@link #offer(Object)}. + * + * @return copy of existing deque extended by new element in last place + */ + @CheckReturnValue + PersistentDeque copyAndOffer(T t); + + /** + * Returns a copy of the deque to which the provided element has been added in first place. + * Replacement for {@link #offerFirst(Object)}. + * + * @return copy of existing deque extended by new element in first place + */ + @CheckReturnValue + PersistentDeque copyAndOfferFirst(T t); + + /** + * Returns a copy of the deque to which the provided element has been added in last place. + * Replacement for {@link #offerLast(Object)}. + * + * @return copy of existing deque extended by new element in last place + */ + @CheckReturnValue + PersistentDeque copyAndOfferLast(T t); + + /** + * Returns a copy of this deque from which the first element has been removed or null if the deque + * is empty. Replacement for {@link #poll()}. + * + * @return copy of this deque from which the first element has been removed, null if deque empty + */ + @CheckReturnValue + @Nullable + PersistentDeque copyAndPoll(); + + /** + * Returns a copy of this deque from which the first element has been removed or null if the deque + * is empty. Replacement for {@link #pollFirst()}. + * + * @return copy of this deque from which the first element has been removed, null if deque empty + */ + @CheckReturnValue + @Nullable + PersistentDeque copyAndPollFirst(); + + /** + * Returns a copy of this deque from which the last element has been removed or null if the deque + * is empty. Replacement for {@link #pollLast()}. + * + * @return copy of this deque from which the last element has been removed, null if deque empty + */ + @CheckReturnValue + @Nullable + PersistentDeque copyAndPollLast(); + + /** + * Returns a copy of this deque from which the first element has been removed. Replacement for + * {@link #pop()}. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the first element has been removed + */ + @CheckReturnValue + PersistentDeque copyAndPop(); + + /** + * Returns a copy of this deque to which the provided object has been pushed onto the stack + * represented by this deque. Replacement for {@link #push(Object)}. + * + * @throws NullPointerException if provided object is null + * @return copy of this deque to which the provided element has been added in first place + */ + @CheckReturnValue + PersistentDeque copyAndPush(T t); + + /** + * Returns a copy of this deque from which the first element has been removed. Replacement for + * {@link #remove()}. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the first element has been removed + */ + @CheckReturnValue + PersistentDeque copyAndRemove(); + + /** + * Returns a copy of this deque from which the first occurrence of the provided element has been + * removed. Replacement for {@link #remove(Object)}. + * + * @return copy of this deque from which the first occurrence of the provided element has been + * removed + */ + @CheckReturnValue + PersistentDeque copyAndRemove(T t); + + /** + * Returns a copy of this deque from which the first element has been removed. Replacement for + * {@link #removeFirst()}. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the first element has been removed + */ + @CheckReturnValue + PersistentDeque copyAndRemoveFirst(); + + /** + * Returns a copy of this deque from which the first occurrence of the provided element has been + * removed. Replacement for {@link #removeFirstOccurrence(Object)}. + * + * @return copy of this deque from which the first occurrence of the provided element has been + * removed + */ + @CheckReturnValue + PersistentDeque copyAndRemoveFirstOccurrence(T t); + + /** + * Returns a copy of this deque from which the last element has been removed. Replacement for + * {@link #removeLast()}. + * + * @throws NoSuchElementException if deque empty + * @return copy of this deque from which the last element has been removed + */ + @CheckReturnValue + PersistentDeque copyAndRemoveLast(); + + /** + * Returns a copy of this deque from which the last occurrence of the provided element has been + * removed. Replacement for {@link #removeLastOccurrence(Object)}. + * + * @return copy of this deque from which the last occurrence of the provided element has been + * removed + */ + @CheckReturnValue + PersistentDeque copyAndRemoveLastOccurrence(T t); + + /** + * Retrieves element at top of deque. + * + * @throws NoSuchElementException if deque empty + * @return element at top of deque + */ + T element(); + + /** + * Retrieves element at top of deque. + * + * @throws NoSuchElementException if deque empty + * @return element at top of deque + */ + T getFirst(); + + /** + * Retrieves element at bottom of deque. + * + * @throws NoSuchElementException if deque empty + * @return element at bottom of deque + */ + T getLast(); + + /** + * Retrieves element at top of deque or returns null in the case of an empty deque. + * + * @return element at top of deque; null if deque empty + */ + T peek(); + + /** + * Retrieves element at top of deque or returns null in the case of an empty deque. + * + * @return element at top of deque; null if deque empty + */ + T peekFirst(); + + /** + * Retrieves element at bottom of deque or returns null in the case of an empty deque. + * + * @return element at bottom of deque; null if deque empty + */ + T peekLast(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean add(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + void addFirst(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + void addLast(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean offer(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean offerFirst(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean offerLast(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T poll(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T pollFirst(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T pollLast(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T pop(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + void push(T t); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T remove(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean remove(Object o); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T removeFirst(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean removeFirstOccurrence(Object o); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + T removeLast(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean removeLastOccurrence(Object o); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean addAll(Collection c); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + void clear(); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean containsAll(Collection c); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean removeAll(Collection c); + + /** + * @throws UnsupportedOperationException Always. + * @deprecated Unsupported operation. + */ + @Deprecated + @Override + @DoNotCall + boolean retainAll(Collection c); +} diff --git a/src/org/sosy_lab/common/collect/PersistentDequeTest.java b/src/org/sosy_lab/common/collect/PersistentDequeTest.java new file mode 100644 index 000000000..420f6a72e --- /dev/null +++ b/src/org/sosy_lab/common/collect/PersistentDequeTest.java @@ -0,0 +1,272 @@ +// This file is part of SoSy-Lab Common, +// a library of useful utilities: +// https://github.com/sosy-lab/java-common-lib +// +// SPDX-FileCopyrightText: 2026 Dirk Beyer +// +// SPDX-License-Identifier: Apache-2.0 + +package org.sosy_lab.common.collect; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import java.util.NoSuchElementException; +import org.junit.Test; + +public class PersistentDequeTest { + + @Test + public void testEmptyDequeCreationOnString() { + PersistentDeque emptyStringDeque = PersistentBalancingDoubleListDeque.of(); + + assertThat(emptyStringDeque.isEmpty()).isTrue(); + + assertThat(emptyStringDeque.size()).isEqualTo(0); + } + + @Test + public void testAllInsertionOperationsOnString() { + PersistentDeque testStringDeque = PersistentBalancingDoubleListDeque.of(); + + testStringDeque = testStringDeque.copyAndPush("c"); + testStringDeque = testStringDeque.copyAndAddFirst("b"); + testStringDeque = testStringDeque.copyAndOfferFirst("a"); + testStringDeque = testStringDeque.copyAndAdd("d"); + testStringDeque = testStringDeque.copyAndOffer("e"); + testStringDeque = testStringDeque.copyAndAddLast("f"); + testStringDeque = testStringDeque.copyAndOfferLast("g"); + + assertThat(testStringDeque.size()).isEqualTo(7); + assertThat(testStringDeque).containsExactly("a", "b", "c", "d", "e", "f", "g").inOrder(); + } + + @Test + public void testAllRemoveOperationsOnString() { + PersistentDeque testStringDeque = PersistentBalancingDoubleListDeque.of(); + + testStringDeque = testStringDeque.copyAndAdd("a"); + testStringDeque = testStringDeque.copyAndAdd("b"); + testStringDeque = testStringDeque.copyAndAdd("c"); + testStringDeque = testStringDeque.copyAndAdd("d"); + testStringDeque = testStringDeque.copyAndAdd("e"); + testStringDeque = testStringDeque.copyAndAdd("f"); + testStringDeque = testStringDeque.copyAndAdd("g"); + + testStringDeque = testStringDeque.copyAndRemove(); + assertThat(testStringDeque).containsExactly("b", "c", "d", "e", "f", "g").inOrder(); + + testStringDeque = testStringDeque.copyAndRemoveFirst(); + assertThat(testStringDeque).containsExactly("c", "d", "e", "f", "g").inOrder(); + + testStringDeque = testStringDeque.copyAndRemoveLast(); + assertThat(testStringDeque).containsExactly("c", "d", "e", "f").inOrder(); + + testStringDeque = testStringDeque.copyAndPop(); + assertThat(testStringDeque).containsExactly("d", "e", "f").inOrder(); + + testStringDeque = testStringDeque.copyAndPoll(); + assertThat(testStringDeque).containsExactly("e", "f").inOrder(); + + testStringDeque = testStringDeque.copyAndPollFirst(); + assertThat(testStringDeque).containsExactly("f").inOrder(); + + testStringDeque = testStringDeque.copyAndAdd("g"); + testStringDeque = testStringDeque.copyAndPollLast(); + assertThat(testStringDeque).containsExactly("f").inOrder(); + + testStringDeque = PersistentBalancingDoubleListDeque.of(); + testStringDeque = testStringDeque.copyAndAdd("a"); + testStringDeque = testStringDeque.copyAndAdd("b"); + testStringDeque = testStringDeque.copyAndAdd("a"); + testStringDeque = testStringDeque.copyAndAdd("b"); + testStringDeque = testStringDeque.copyAndAdd("a"); + testStringDeque = testStringDeque.copyAndAdd("b"); + + testStringDeque = testStringDeque.copyAndRemoveFirstOccurrence("b"); + assertThat(testStringDeque).containsExactly("a", "a", "b", "a", "b").inOrder(); + + testStringDeque = testStringDeque.copyAndRemoveLastOccurrence("a"); + assertThat(testStringDeque).containsExactly("a", "a", "b", "b").inOrder(); + } + + @Test + public void testAllGetOperationsOnString() { + final PersistentDeque emptyStringDeque = PersistentBalancingDoubleListDeque.of(); + PersistentDeque testStringDeque = PersistentBalancingDoubleListDeque.of(); + + // check correct behaviour (exception vs. returning null) on empty deque + assertThrows(NoSuchElementException.class, emptyStringDeque::getFirst); + assertThrows(NoSuchElementException.class, emptyStringDeque::element); + assertThrows(NoSuchElementException.class, emptyStringDeque::getLast); + assertThat(emptyStringDeque.peek()).isEqualTo(null); + assertThat(emptyStringDeque.peekFirst()).isEqualTo(null); + assertThat(emptyStringDeque.peekLast()).isEqualTo(null); + + // ensures that single element is correctly identified as both head and tail in deque of size 1 + testStringDeque = testStringDeque.copyAndAdd("a"); + assertThat(testStringDeque.getFirst()).isEqualTo("a"); + assertThat(testStringDeque.element()).isEqualTo("a"); + assertThat(testStringDeque.peek()).isEqualTo("a"); + assertThat(testStringDeque.peekFirst()).isEqualTo("a"); + assertThat(testStringDeque.getLast()).isEqualTo("a"); + assertThat(testStringDeque.peekLast()).isEqualTo("a"); + + // ensures that head and tail are correctly identified in deque of size > 1 + testStringDeque = testStringDeque.copyAndAdd("b"); + assertThat(testStringDeque.getFirst()).isEqualTo("a"); + assertThat(testStringDeque.element()).isEqualTo("a"); + assertThat(testStringDeque.peek()).isEqualTo("a"); + assertThat(testStringDeque.peekFirst()).isEqualTo("a"); + assertThat(testStringDeque.getLast()).isEqualTo("b"); + assertThat(testStringDeque.peekLast()).isEqualTo("b"); + } + + @Test + public void testEqualsOnString() { + PersistentDeque testStringDeque1 = PersistentBalancingDoubleListDeque.of(); + PersistentDeque testStringDeque2 = PersistentBalancingDoubleListDeque.of(); + + // test on empty deque + assertThat(testStringDeque1.equals(testStringDeque2)).isTrue(); + + // deques should be equal due to same order despite elements potentially being distributed + // differently between top and bottom lists + testStringDeque1 = testStringDeque1.copyAndAddFirst("c"); + testStringDeque1 = testStringDeque1.copyAndAddFirst("b"); + testStringDeque1 = testStringDeque1.copyAndAddFirst("a"); + + testStringDeque2 = testStringDeque2.copyAndAddLast("a"); + testStringDeque2 = testStringDeque2.copyAndAddLast("b"); + testStringDeque2 = testStringDeque2.copyAndAddLast("c"); + + assertThat(testStringDeque1.equals(testStringDeque2)).isTrue(); + } + + @Test + public void testEmptyDequeCreationOnInteger() { + PersistentDeque emptyIntegerDeque = PersistentBalancingDoubleListDeque.of(); + + assertThat(emptyIntegerDeque.isEmpty()).isTrue(); + + assertThat(emptyIntegerDeque.size()).isEqualTo(0); + } + + @Test + public void testAllInsertionOperationsOnInteger() { + PersistentDeque testIntegerDeque = PersistentBalancingDoubleListDeque.of(); + + testIntegerDeque = testIntegerDeque.copyAndPush(3); + testIntegerDeque = testIntegerDeque.copyAndAddFirst(2); + testIntegerDeque = testIntegerDeque.copyAndOfferFirst(1); + testIntegerDeque = testIntegerDeque.copyAndAdd(4); + testIntegerDeque = testIntegerDeque.copyAndOffer(5); + testIntegerDeque = testIntegerDeque.copyAndAddLast(6); + testIntegerDeque = testIntegerDeque.copyAndOfferLast(7); + + assertThat(testIntegerDeque.size()).isEqualTo(7); + assertThat(testIntegerDeque).containsExactly(1, 2, 3, 4, 5, 6, 7).inOrder(); + } + + @Test + public void testAllRemoveOperationsOnInteger() { + PersistentDeque testIntegerDeque = PersistentBalancingDoubleListDeque.of(); + + testIntegerDeque = testIntegerDeque.copyAndAdd(1); + testIntegerDeque = testIntegerDeque.copyAndAdd(2); + testIntegerDeque = testIntegerDeque.copyAndAdd(3); + testIntegerDeque = testIntegerDeque.copyAndAdd(4); + testIntegerDeque = testIntegerDeque.copyAndAdd(5); + testIntegerDeque = testIntegerDeque.copyAndAdd(6); + testIntegerDeque = testIntegerDeque.copyAndAdd(7); + + testIntegerDeque = testIntegerDeque.copyAndRemove(); + assertThat(testIntegerDeque).containsExactly(2, 3, 4, 5, 6, 7).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndRemoveFirst(); + assertThat(testIntegerDeque).containsExactly(3, 4, 5, 6, 7).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndRemoveLast(); + assertThat(testIntegerDeque).containsExactly(3, 4, 5, 6).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndPop(); + assertThat(testIntegerDeque).containsExactly(4, 5, 6).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndPoll(); + assertThat(testIntegerDeque).containsExactly(5, 6).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndPollFirst(); + assertThat(testIntegerDeque).containsExactly(6).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndAdd(7); + testIntegerDeque = testIntegerDeque.copyAndPollLast(); + assertThat(testIntegerDeque).containsExactly(6).inOrder(); + + testIntegerDeque = PersistentBalancingDoubleListDeque.of(); + testIntegerDeque = testIntegerDeque.copyAndAdd(1); + testIntegerDeque = testIntegerDeque.copyAndAdd(2); + testIntegerDeque = testIntegerDeque.copyAndAdd(1); + testIntegerDeque = testIntegerDeque.copyAndAdd(2); + testIntegerDeque = testIntegerDeque.copyAndAdd(1); + testIntegerDeque = testIntegerDeque.copyAndAdd(2); + + testIntegerDeque = testIntegerDeque.copyAndRemoveFirstOccurrence(2); + assertThat(testIntegerDeque).containsExactly(1, 1, 2, 1, 2).inOrder(); + + testIntegerDeque = testIntegerDeque.copyAndRemoveLastOccurrence(1); + assertThat(testIntegerDeque).containsExactly(1, 1, 2, 2).inOrder(); + } + + @Test + public void testAllGetOperationsOnInteger() { + final PersistentDeque emptyIntegerDeque = PersistentBalancingDoubleListDeque.of(); + PersistentDeque testIntegerDeque = PersistentBalancingDoubleListDeque.of(); + + // check correct behaviour (exception vs. returning null) on empty deque + assertThrows(NoSuchElementException.class, emptyIntegerDeque::getFirst); + assertThrows(NoSuchElementException.class, emptyIntegerDeque::element); + assertThrows(NoSuchElementException.class, emptyIntegerDeque::getLast); + assertThat(emptyIntegerDeque.peek()).isEqualTo(null); + assertThat(emptyIntegerDeque.peekFirst()).isEqualTo(null); + assertThat(emptyIntegerDeque.peekLast()).isEqualTo(null); + + // ensures that single element is correctly identified as both head and tail in deque of size 1 + testIntegerDeque = testIntegerDeque.copyAndAdd(1); + assertThat(testIntegerDeque.getFirst()).isEqualTo(1); + assertThat(testIntegerDeque.element()).isEqualTo(1); + assertThat(testIntegerDeque.peek()).isEqualTo(1); + assertThat(testIntegerDeque.peekFirst()).isEqualTo(1); + assertThat(testIntegerDeque.getLast()).isEqualTo(1); + assertThat(testIntegerDeque.peekLast()).isEqualTo(1); + + // ensures that head and tail are correctly identified in deque of size > 1 + testIntegerDeque = testIntegerDeque.copyAndAdd(2); + assertThat(testIntegerDeque.getFirst()).isEqualTo(1); + assertThat(testIntegerDeque.element()).isEqualTo(1); + assertThat(testIntegerDeque.peek()).isEqualTo(1); + assertThat(testIntegerDeque.peekFirst()).isEqualTo(1); + assertThat(testIntegerDeque.getLast()).isEqualTo(2); + assertThat(testIntegerDeque.peekLast()).isEqualTo(2); + } + + @Test + public void testEqualsOnInteger() { + PersistentDeque testIntegerDeque1 = PersistentBalancingDoubleListDeque.of(); + PersistentDeque testIntegerDeque2 = PersistentBalancingDoubleListDeque.of(); + + // test on empty deque + assertThat(testIntegerDeque1.equals(testIntegerDeque2)).isTrue(); + + // deques should be equal due to same order despite elements potentially being distributed + // differently between top and bottom lists + testIntegerDeque1 = testIntegerDeque1.copyAndAddFirst(3); + testIntegerDeque1 = testIntegerDeque1.copyAndAddFirst(2); + testIntegerDeque1 = testIntegerDeque1.copyAndAddFirst(1); + + testIntegerDeque2 = testIntegerDeque2.copyAndAddLast(1); + testIntegerDeque2 = testIntegerDeque2.copyAndAddLast(2); + testIntegerDeque2 = testIntegerDeque2.copyAndAddLast(3); + + assertThat(testIntegerDeque1.equals(testIntegerDeque2)).isTrue(); + } +}