Adapt RedisList to API changes in Java 21 SequencedCollections.

Closes #2602
Original pull request: #2608
This commit is contained in:
John Blum
2023-06-12 18:55:42 -07:00
committed by Mark Paluch
parent 5996078fe5
commit a05b25abe5
5 changed files with 1382 additions and 148 deletions

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.support.collections;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
@@ -39,42 +40,32 @@ import org.springframework.util.Assert;
* @author Costin Leau
* @author Christoph Strobl
* @author Mark Paluch
* @author John Blum
*/
public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements RedisList<E> {
private final BoundListOperations<String, E> listOps;
private volatile int maxSize = 0;
private volatile boolean capped = false;
private class DefaultRedisListIterator extends RedisIterator<E> {
private volatile int maxSize = 0;
public DefaultRedisListIterator(Iterator<E> delegate) {
super(delegate);
}
@Override
protected void removeFromRedisStorage(E item) {
DefaultRedisList.this.remove(item);
}
}
private final BoundListOperations<String, E> listOps;
/**
* Constructs a new, uncapped {@link DefaultRedisList} instance.
*
* @param key Redis key of this list.
* @param operations {@link RedisOperations} for the value type of this list.
* @param operations {@link RedisOperations} used to retrieve values of the declared {@link E type} from this list.
*/
public DefaultRedisList(String key, RedisOperations<String, E> operations) {
this(operations.boundListOps(key));
}
/**
* Constructs a new {@link DefaultRedisList} instance.
* Constructs a new {@link DefaultRedisList} instance constrained to the given {@link Integer max size}.
*
* @param key Redis key of this list.
* @param operations {@link RedisOperations} for the value type of this list.
* @param maxSize
* @param operations {@link RedisOperations} used to retrieve values of the declared {@link E type} from this list.
* @param maxSize {@link Integer maximum number of elements} allowed to be stored in this list.
* @since 2.6
*/
public DefaultRedisList(String key, RedisOperations<String, E> operations, int maxSize) {
@@ -91,10 +82,10 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
}
/**
* Constructs a new {@link DefaultRedisList} instance.
* Constructs a new {@link DefaultRedisList} instance constrained to the given {@link Integer max size}.
*
* @param boundOps {@link BoundListOperations} for the value type of this list.
* @param maxSize
* @param maxSize {@link Integer maximum number of elements} allowed to be stored in this list.
*/
public DefaultRedisList(BoundListOperations<String, E> boundOps, int maxSize) {
@@ -165,6 +156,7 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
return result;
}
@SuppressWarnings("unchecked")
private void potentiallyCap(RedisList<E> destination) {
if (destination instanceof DefaultRedisList) {
((DefaultRedisList<Object>) destination).cap();
@@ -217,11 +209,12 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
@Override
public boolean remove(Object o) {
Long result = listOps.remove(1, o);
return (result != null && result.longValue() > 0);
return result != null && result > 0L;
}
@Override
public void add(int index, E element) {
if (index == 0) {
listOps.leftPush(element);
cap();
@@ -244,23 +237,26 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
public boolean addAll(int index, Collection<? extends E> collection) {
// insert collection in reverse
if (index == 0) {
Collection<? extends E> reverseC = CollectionUtils.reverse(c);
for (E e : reverseC) {
listOps.leftPush(e);
Collection<? extends E> reverseCollection = CollectionUtils.reverse(collection);
for (E element : reverseCollection) {
listOps.leftPush(element);
cap();
}
return true;
}
int size = size();
if (index == size()) {
for (E e : c) {
listOps.rightPush(e);
for (E element : collection) {
listOps.rightPush(element);
cap();
}
return true;
@@ -275,34 +271,40 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
@Override
public E get(int index) {
if (index < 0 || index > size()) {
throw new IndexOutOfBoundsException();
}
return listOps.index(index);
}
@Override
public int indexOf(Object o) {
@SuppressWarnings("unchecked")
public int indexOf(Object element) {
Long index = listOps.indexOf((E) element);
Long index = listOps.indexOf((E) o);
return index != null ? index.intValue() : -1;
}
@Override
public int lastIndexOf(Object o) {
@SuppressWarnings("unchecked")
public int lastIndexOf(Object element) {
Long index = listOps.lastIndexOf((E) element);
Long index = listOps.lastIndexOf((E) o);
return index != null ? index.intValue() : -1;
}
@Override
public ListIterator<E> listIterator() {
throw new UnsupportedOperationException();
return listIterator(0);
}
@Override
public ListIterator<E> listIterator(int index) {
throw new UnsupportedOperationException();
return new ListItr(index);
}
@Override
@@ -311,10 +313,10 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
}
@Override
public E set(int index, E e) {
public E set(int index, E element) {
E object = get(index);
listOps.set(index, e);
listOps.set(index, element);
return object;
}
@@ -338,8 +340,8 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
}
@Override
public boolean offer(E e) {
listOps.rightPush(e);
public boolean offer(E element) {
listOps.rightPush(element);
cap();
return true;
}
@@ -372,14 +374,14 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
//
@Override
public void addFirst(E e) {
listOps.leftPush(e);
public void addFirst(E element) {
listOps.leftPush(element);
cap();
}
@Override
public void addLast(E e) {
add(e);
public void addLast(E element) {
add(element);
}
@Override
@@ -396,22 +398,22 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
@Override
public E getLast() {
E e = peekLast();
if (e == null) {
E element = peekLast();
if (element == null) {
throw new NoSuchElementException();
}
return e;
return element;
}
@Override
public boolean offerFirst(E e) {
addFirst(e);
public boolean offerFirst(E element) {
addFirst(element);
return true;
}
@Override
public boolean offerLast(E e) {
addLast(e);
public boolean offerLast(E element) {
addLast(element);
return true;
}
@@ -442,16 +444,16 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
@Override
public E pop() {
E e = poll();
if (e == null) {
E element = poll();
if (element == null) {
throw new NoSuchElementException();
}
return e;
return element;
}
@Override
public void push(E e) {
addFirst(e);
public void push(E element) {
addFirst(element);
}
@Override
@@ -460,52 +462,52 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
}
@Override
public boolean removeFirstOccurrence(Object o) {
return remove(o);
public boolean removeFirstOccurrence(Object element) {
return remove(element);
}
@Override
public E removeLast() {
E e = pollLast();
if (e == null) {
E element = pollLast();
if (element == null) {
throw new NoSuchElementException();
}
return e;
return element;
}
@Override
public boolean removeLastOccurrence(Object o) {
Long result = listOps.remove(-1, o);
return (result != null && result.longValue() > 0);
public boolean removeLastOccurrence(Object element) {
Long result = listOps.remove(-1, element);
return result != null && result > 0L;
}
//
// BlockingQueue
//
@Override
public int drainTo(Collection<? super E> c, int maxElements) {
if (this.equals(c)) {
public int drainTo(Collection<? super E> collection, int maxElements) {
if (this.equals(collection)) {
throw new IllegalArgumentException("Cannot drain a queue to itself");
}
int size = size();
int loop = (size >= maxElements ? maxElements : size);
int loop = (Math.min(size(), maxElements));
for (int index = 0; index < loop; index++) {
c.add(poll());
collection.add(poll());
}
return loop;
}
@Override
public int drainTo(Collection<? super E> c) {
return drainTo(c, size());
public int drainTo(Collection<? super E> collection) {
return drainTo(collection, size());
}
@Override
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
return offer(e);
public boolean offer(E element, long timeout, TimeUnit unit) throws InterruptedException {
return offer(element);
}
@Override
@@ -515,8 +517,8 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
}
@Override
public void put(E e) throws InterruptedException {
offer(e);
public void put(E element) throws InterruptedException {
offer(element);
}
@Override
@@ -535,13 +537,13 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
//
@Override
public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException {
return offerFirst(e);
public boolean offerFirst(E element, long timeout, TimeUnit unit) {
return offerFirst(element);
}
@Override
public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException {
return offerLast(e);
public boolean offerLast(E element, long timeout, TimeUnit unit) {
return offerLast(element);
}
@Override
@@ -552,18 +554,18 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
@Override
@Nullable
public E pollLast(long timeout, TimeUnit unit) throws InterruptedException {
public E pollLast(long timeout, TimeUnit unit) {
return listOps.rightPop(timeout, unit);
}
@Override
public void putFirst(E e) throws InterruptedException {
add(e);
public void putFirst(E element) {
add(element);
}
@Override
public void putLast(E e) throws InterruptedException {
put(e);
public void putLast(E element) throws InterruptedException {
put(element);
}
@Override
@@ -574,7 +576,7 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
@Override
@Nullable
public E takeLast() throws InterruptedException {
public E takeLast() {
return pollLast(0, TimeUnit.SECONDS);
}
@@ -592,4 +594,131 @@ public class DefaultRedisList<E> extends AbstractRedisCollection<E> implements R
listOps.trim(0, maxSize - 1);
}
}
private class DefaultRedisListIterator extends RedisIterator<E> {
public DefaultRedisListIterator(Iterator<E> delegate) {
super(delegate);
}
@Override
protected void removeFromRedisStorage(E item) {
DefaultRedisList.this.remove(item);
}
}
private class Itr implements Iterator<E> {
/**
* Index of the {@link E element} in this iteration to be returned by subsequent call to {@link #next()}.
*/
int cursor = 0;
/**
* Index of the {@link E element} in this iteration last returned by a call to {@link #next()}.
*/
int lastReturnedElementIndex = -1;
@Nullable
E lastReturnedElement;
@Override
public boolean hasNext() {
return this.cursor < size();
}
@Override
public E next() {
try {
int index = this.cursor;
this.lastReturnedElement = get(index);
this.cursor = index + 1;
this.lastReturnedElementIndex = index;
return this.lastReturnedElement;
} catch (IndexOutOfBoundsException cause) {
throw new NoSuchElementException(cause);
}
}
@Override
@SuppressWarnings("all")
public void remove() {
Assert.state(this.lastReturnedElement != null,
"Next must be called before remove");
if (!DefaultRedisList.this.remove(this.lastReturnedElement)) {
throw new ConcurrentModificationException();
}
this.lastReturnedElementIndex = -1;
this.lastReturnedElement = null;
this.cursor--;
}
}
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
this.cursor = index;
}
@Override
public boolean hasPrevious() {
return this.cursor > 0;
}
@Override
public int nextIndex() {
return this.cursor;
}
@Override
public int previousIndex() {
return this.cursor - 1;
}
@Override
public void add(E element) {
try {
int index = this.cursor;
DefaultRedisList.this.add(index, element);
this.lastReturnedElementIndex = -1;
this.lastReturnedElement = null;
this.cursor = index + 1;
} catch (IndexOutOfBoundsException cause) {
throw new ConcurrentModificationException();
}
}
@Override
public E previous() {
try {
int index = this.cursor - 1;
this.lastReturnedElement = get(index);
this.lastReturnedElementIndex = index;
this.cursor = index;
return this.lastReturnedElement;
} catch (IndexOutOfBoundsException cause) {
throw new NoSuchElementException();
}
}
@Override
public void set(E element) {
Assert.state(this.lastReturnedElement != null,
"next() or previous() must be called before set(:E)");
try {
DefaultRedisList.this.set(this.lastReturnedElementIndex, element);
} catch (IndexOutOfBoundsException cause) {
throw new ConcurrentModificationException();
}
}
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.redis.support.collections;
import static org.springframework.data.redis.connection.RedisListCommands.*;
import static org.springframework.data.redis.connection.RedisListCommands.Direction;
import java.time.Duration;
import java.util.Deque;
@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
*
* @author Costin Leau
* @author Mark Paluch
* @author John Blum
*/
public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque<E> {
@@ -51,11 +52,12 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
}
/**
* Constructs a new {@link RedisList} instance.
* Factory method used to construct a new {@link RedisList} from a Redis list reference by the given {@link String
* key}.
*
* @param key Redis key of this list.
* @param operations {@link RedisOperations} for the value type of this list.
* @param maxSize
* @param maxSize {@link Integer} used to constrain the size of the list.
* @since 2.6
*/
static <E> RedisList<E> create(String key, RedisOperations<String, E> operations, int maxSize) {
@@ -73,10 +75,10 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
}
/**
* Constructs a new {@link DefaultRedisList} instance.
* Constructs a new {@link DefaultRedisList}.
*
* @param boundOps {@link BoundListOperations} for the value type of this list.
* @param maxSize
* @param maxSize {@link Integer} constraining the size of the list.
* @since 2.6
*/
static <E> RedisList<E> create(BoundListOperations<String, E> boundOps, int maxSize) {
@@ -98,6 +100,25 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
@Nullable
E moveFirstTo(RedisList<E> destination, Direction destinationPosition);
/**
* Atomically returns and removes the first element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
* at {@link RedisList destination}.
* <p>
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param destination must not be {@literal null}.
* @param destinationPosition must not be {@literal null}.
* @param timeout
* @param unit must not be {@literal null}.
* @return
* @since 2.6
* @see Direction#first()
* @see Direction#last()
*/
@Nullable
E moveFirstTo(RedisList<E> destination, Direction destinationPosition, long timeout, TimeUnit unit);
/**
* Atomically returns and removes the first element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
@@ -123,25 +144,6 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
TimeoutUtils.toMillis(timeout.toMillis(), TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
}
/**
* Atomically returns and removes the first element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
* at {@link RedisList destination}.
* <p>
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param destination must not be {@literal null}.
* @param destinationPosition must not be {@literal null}.
* @param timeout
* @param unit must not be {@literal null}.
* @return
* @since 2.6
* @see Direction#first()
* @see Direction#last()
*/
@Nullable
E moveFirstTo(RedisList<E> destination, Direction destinationPosition, long timeout, TimeUnit unit);
/**
* Atomically returns and removes the last element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
@@ -157,6 +159,25 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
@Nullable
E moveLastTo(RedisList<E> destination, Direction destinationPosition);
/**
* Atomically returns and removes the last element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
* at {@link RedisList destination}.
* <p>
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param destination must not be {@literal null}.
* @param destinationPosition must not be {@literal null}.
* @param timeout
* @param unit must not be {@literal null}.
* @return
* @since 2.6
* @see Direction#first()
* @see Direction#last()
*/
@Nullable
E moveLastTo(RedisList<E> destination, Direction destinationPosition, long timeout, TimeUnit unit);
/**
* Atomically returns and removes the last element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
@@ -182,25 +203,6 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
TimeoutUtils.toMillis(timeout.toMillis(), TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
}
/**
* Atomically returns and removes the last element of the list stored at the bound key, and pushes the element at the
* first/last element (head/tail depending on the {@link Direction destinationPosition} argument) of the list stored
* at {@link RedisList destination}.
* <p>
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param destination must not be {@literal null}.
* @param destinationPosition must not be {@literal null}.
* @param timeout
* @param unit must not be {@literal null}.
* @return
* @since 2.6
* @see Direction#first()
* @see Direction#last()
*/
@Nullable
E moveLastTo(RedisList<E> destination, Direction destinationPosition, long timeout, TimeUnit unit);
/**
* Get elements between {@code start} and {@code end} from list at the bound key.
*
@@ -229,4 +231,92 @@ public interface RedisList<E> extends RedisCollection<E>, List<E>, BlockingDeque
* @see <a href="https://redis.io/commands/ltrim">Redis Documentation: LTRIM</a>
*/
RedisList<E> trim(long start, long end);
/**
* {@inheritDoc}
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @param element element to be added to the head of the collection.
*/
@Override
default void addFirst(E element) {
add(0, element);
}
/**
* {@inheritDoc}
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @param element element to be added to be added the end of the collection.
*/
default void addLast(E element) {
add(element);
}
/**
* {@inheritDoc}
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @return the head of this {@link Deque}.
*/
@Nullable
default E getFirst() {
return peekFirst();
}
/**
* {@inheritDoc}
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @return the tail of this {@link Deque}.
*/
@Nullable
default E getLast() {
return peekLast();
}
/**
* {@inheritDoc}
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @return the head of this {@link Deque}.
*/
@Nullable
default E removeFirst() {
return pollFirst();
}
/**
* {@inheritDoc}
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @return the tail of this {@link Deque}.
*/
@Nullable
default E removeLast() {
return pollLast();
}
/**
* Returns a reverse-ordered view of this collection.
* <p>
* The encounter order of elements returned by the view is the inverse of the encounter order of the elements
* stored in this collection. The reverse ordering affects all order-sensitive operations, including any operations
* on further views 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.
* <p>
* This method is forward-compatible with Java 21 {@literal SequencedCollections}.
*
* @return a reverse-ordered view of this collection.
*/
default RedisList<E> reversed() {
return new ReversedRedisListView<>(this);
}
}

View File

@@ -0,0 +1,739 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.support.collections;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.TimeUnit;
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 org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisListCommands.Direction;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.lang.Nullable;
/**
* Implementation and view of an existing {@link RedisList} where the elements in the list (deque) are returned in
* reverse order.
*
* @author John Blum
* @author Mark Paluch
* @param <E> {@link Class type} of the {@link Object elements} contained in the underlying, wrapped {@link RedisList}.
* @since 3.2
*/
class ReversedRedisListView<E> implements RedisList<E> {
private final RedisList<E> base;
ReversedRedisListView(RedisList<E> list) {
this.base = list;
}
// ========== RedisCollection ==========
@Override
public String getKey() {
return this.base.getKey();
}
@Nullable
@Override
public Long getExpire() {
return this.base.getExpire();
}
@Override
public RedisOperations<String, ?> getOperations() {
return this.base.getOperations();
}
@Nullable
@Override
public DataType getType() {
return this.base.getType();
}
@Nullable
@Override
public Boolean expire(long timeout, TimeUnit unit) {
return this.base.expire(timeout, unit);
}
@Nullable
@Override
public Boolean expireAt(Date date) {
return this.base.expireAt(date);
}
@Nullable
@Override
public Boolean persist() {
return this.base.persist();
}
@Nullable
@Override
public E moveFirstTo(RedisList<E> destination, Direction destinationPosition) {
return this.base.moveLastTo(destination, destinationPosition);
}
@Nullable
@Override
public E moveFirstTo(RedisList<E> destination, Direction destinationPosition, long timeout, TimeUnit unit) {
return base.moveLastTo(destination, destinationPosition, timeout, unit);
}
@Nullable
@Override
public E moveLastTo(RedisList<E> destination, Direction destinationPosition) {
return base.moveFirstTo(destination, destinationPosition);
}
@Nullable
@Override
public E moveLastTo(RedisList<E> destination, Direction destinationPosition, long timeout, TimeUnit unit) {
return base.moveFirstTo(destination, destinationPosition, timeout, unit);
}
@Override
public List<E> range(long start, long end) {
return this.base.range(end, start);
}
@Override
public void rename(String newKey) {
this.base.rename(newKey);
}
@Override
public RedisList<E> trim(int start, int end) {
this.base.trim(end, start);
return this;
}
@Override
public RedisList<E> trim(long start, long end) {
this.base.trim(end, start);
return this;
}
// ========== Iterable ==========
@Override
public void forEach(Consumer<? super E> action) {
for (E e : this)
action.accept(e);
}
@Override
public Iterator<E> iterator() {
return new DescendingIterator();
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliteratorUnknownSize(new DescendingIterator(), 0);
}
// ========== Collection ==========
@Override
public boolean add(E element) {
this.base.add(0, element);
return true;
}
@Override
@SuppressWarnings("unchecked")
public boolean addAll(Collection<? extends E> collection) {
return !org.springframework.util.CollectionUtils.isEmpty(collection)
&& this.base.addAll(0, Arrays.asList(reverse((E[]) collection.toArray())));
}
@Override
public void clear() {
this.base.clear();
}
@Override
public boolean contains(Object element) {
return this.base.contains(element);
}
@Override
public boolean containsAll(Collection<?> collection) {
return this.base.containsAll(collection);
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof List<?> that)) {
return false;
}
ListIterator<E> thisListIterator = this.listIterator();
ListIterator<?> thatListIterator = that.listIterator();
while (thisListIterator.hasNext() && thatListIterator.hasNext()) {
if (!(Objects.equals(thisListIterator.next(), thatListIterator.next()))) {
return false;
}
}
return !(thisListIterator.hasNext() || thatListIterator.hasNext());
}
public int hashCode() {
int hashCode = 1;
for (E element : this) {
hashCode = 31 * hashCode + Objects.hashCode(element);
}
return hashCode;
}
@Override
public boolean isEmpty() {
return this.base.isEmpty();
}
@Override
public Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
@Override
public boolean remove(Object element) {
Iterator<E> it = iterator();
while (it.hasNext()) {
if (Objects.equals(element, it.next())) {
it.remove();
return true;
}
}
return false;
}
@Override
public boolean removeAll(Collection<?> collection) {
Objects.requireNonNull(collection);
Iterator<?> it = iterator();
boolean modified = false;
while (it.hasNext()) {
if (collection.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
@Override
public boolean retainAll(Collection<?> collection) {
Objects.requireNonNull(collection);
Iterator<E> it = iterator();
boolean modified = false;
while (it.hasNext()) {
if (!collection.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
@Override
public int size() {
return this.base.size();
}
@Override
public Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
@Override
public Object[] toArray() {
return reverse(this.base.toArray());
}
@Override
public <T> T[] toArray(T[] array) {
return toArrayReversed(this.base, array);
}
@Override
public <T> T[] toArray(IntFunction<T[]> generator) {
return reverse(this.base.toArray(generator));
}
public String toString() {
Iterator<E> it = iterator();
if (!it.hasNext()) {
return "[]";
}
StringBuilder stringBuilder = new StringBuilder("[");
for (;;) {
E element = it.next();
stringBuilder.append(element == this ? "(this Collection)" : element);
if (!it.hasNext()) {
return stringBuilder.append(']').toString();
}
stringBuilder.append(',').append(' ');
}
}
// ========== List ==========
@Override
public void add(int index, E element) {
int size = this.base.size();
checkClosedRange(index, size);
this.base.add(size - index, element);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
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(reverse(adds)));
return true;
}
}
@Override
public E get(int index) {
int size = base.size();
Objects.checkIndex(index, size);
return this.base.get(size - index - 1);
}
@Override
@SuppressWarnings("all")
public int indexOf(Object element) {
int lastIndex = this.base.lastIndexOf(element);
return lastIndex == -1 ? -1 : this.base.size() - lastIndex - 1;
}
@Override
@SuppressWarnings("all")
public int lastIndexOf(Object element) {
int index = this.base.indexOf(element);
return index == -1 ? -1 : this.base.size() - index - 1;
}
@Override
public ListIterator<E> listIterator() {
return new DescendingListIterator(base.size(), 0);
}
@Override
public ListIterator<E> listIterator(int index) {
int size = this.base.size();
checkClosedRange(index, size);
return new DescendingListIterator(size, index);
}
@Override
public E remove(int index) {
int size = this.base.size();
Objects.checkIndex(index, size);
return this.base.remove(size - index - 1);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
return this.base.removeIf(filter);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
this.base.replaceAll(operator);
}
@Override
public void sort(Comparator<? super E> comparator) {
this.base.sort(Collections.reverseOrder(comparator));
}
@Override
public E set(int index, E element) {
int size = this.base.size();
Objects.checkIndex(index, size);
return this.base.set(size - index - 1, element);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
// ========== BlockingDeque ==========
@Override
public E element() {
return peekLast();
}
@Override
public boolean offer(E element) {
return this.base.offerFirst(element);
}
@Override
public boolean offer(E element, long timeout, TimeUnit unit) throws InterruptedException {
return this.base.offerFirst(element, timeout, unit);
}
@Override
public boolean offerFirst(E element) {
return this.base.offerLast(element);
}
@Override
public boolean offerFirst(E element, long timeout, TimeUnit unit) throws InterruptedException {
return this.base.offerLast(element, timeout, unit);
}
@Override
public boolean offerLast(E element) {
return this.base.offerFirst(element);
}
@Override
public boolean offerLast(E element, long timeout, TimeUnit unit) throws InterruptedException {
return this.base.offerFirst(element, timeout, unit);
}
@Override
public void putFirst(E element) throws InterruptedException {
this.base.putLast(element);
}
@Override
public void putLast(E element) throws InterruptedException {
this.base.putFirst(element);
}
@Nullable
@Override
public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException {
return this.base.pollLast(timeout, unit);
}
@Nullable
@Override
public E pollLast(long timeout, TimeUnit unit) throws InterruptedException {
return this.base.pollFirst(timeout, unit);
}
@Override
public void put(E element) throws InterruptedException {
this.base.offerFirst(element);
}
@Override
public boolean removeFirstOccurrence(Object element) {
return this.base.removeLastOccurrence(element);
}
@Override
public boolean removeLastOccurrence(Object element) {
return this.base.removeFirstOccurrence(element);
}
@Override
public E take() throws InterruptedException {
return takeFirst();
}
@Override
public E takeFirst() throws InterruptedException {
return this.base.takeLast();
}
@Override
public E takeLast() throws InterruptedException {
return this.base.takeFirst();
}
// ========== Deque ==========
@Override
public Iterator<E> descendingIterator() {
return this.base.iterator();
}
@Override
public int drainTo(Collection<? super E> collection) {
return drainTo(collection, size());
}
@Override
public int drainTo(Collection<? super E> collection, int maxElements) {
if (this.equals(collection)) {
throw new IllegalArgumentException("Cannot drain a queue to itself");
}
int loop = Math.min(size(), maxElements);
for (int index = 0; index < loop; index++) {
collection.add(poll());
}
return loop;
}
@Override
public E peek() {
return peekLast();
}
@Override
public E peekFirst() {
return this.base.peekLast();
}
@Override
public E peekLast() {
return this.base.peekFirst();
}
@Override
public E poll() {
return pollLast();
}
@Nullable
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
return pollLast(0, TimeUnit.SECONDS);
}
@Override
public E pollFirst() {
return this.base.pollLast();
}
@Override
public E pollLast() {
return this.base.pollFirst();
}
@Override
public E pop() {
E element = poll();
if (element == null) {
throw new NoSuchElementException();
}
return element;
}
@Override
public void push(E element) {
this.base.addLast(element);
}
@Override
public int remainingCapacity() {
return this.base.remainingCapacity();
}
@Override
public E remove() {
return pollLast();
}
@Override
public RedisList<E> reversed() {
return this.base;
}
class DescendingIterator implements Iterator<E> {
final ListIterator<E> it = base.listIterator(base.size());
@Override
public boolean hasNext() {
return this.it.hasPrevious();
}
@Override
public E next() {
return this.it.previous();
}
@Override
public void remove() {
this.it.remove();
}
}
class DescendingListIterator implements ListIterator<E> {
final ListIterator<E> it;
DescendingListIterator(int size, int position) {
if (position < 0 || position > size) {
String message = String.format("Position [%d] is out of bounds: [0, %d]", position, size);
throw new IndexOutOfBoundsException(message);
}
this.it = base.listIterator(size - position);
}
@Override
public boolean hasNext() {
return it.hasPrevious();
}
@Override
public E next() {
return it.previous();
}
@Override
public boolean hasPrevious() {
return it.hasNext();
}
@Override
public E previous() {
return it.next();
}
@Override
public int nextIndex() {
return base.size() - it.nextIndex();
}
@Override
public int previousIndex() {
return nextIndex() - 1;
}
@Override
public void remove() {
it.remove();
}
@Override
public void set(E e) {
it.set(e);
}
@Override
public void add(E e) {
it.add(e);
it.previous();
}
}
/**
* Reverses the elements of an array in-place.
*
* @param <T> the array component type
* @param array the array to be reversed
* @return the reversed array, always the same array as the argument
*/
static <T> T[] reverse(T[] array) {
int limit = array.length / 2;
for (int i = 0, j = array.length - 1; i < limit; i++, j--) {
swap(array, i, j);
}
return array;
}
private static <T> void swap(T[] array, int indexOne, int indexTwo) {
T element = array[indexOne];
array[indexOne] = array[indexTwo];
array[indexTwo] = element;
}
static <T> T[] toArrayReversed(Collection<?> collection, T[] array) {
T[] newArray = reverse(collection.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;
}
}
static void checkClosedRange(int index, int size) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
}