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);
}
}
}

View File

@@ -15,41 +15,43 @@
*/
package org.springframework.data.redis.support.collections;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assumptions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.test.condition.EnabledOnCommand;
import org.springframework.data.redis.test.extension.parametrized.ParameterizedRedisTest;
/**
* Integration test for RedisList
* Integration tests for RedisList
*
* @author Costin Leau
* @author Jennifer Hickey
* @author Mark Paluch
* @author John Blum
*/
public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedisCollectionIntegrationTests<T> {
protected RedisList<T> list;
/**
* Constructs a new <code>AbstractRedisListTests</code> instance.
* Constructs a new {@link AbstractRedisListIntegrationTests}.
*
* @param factory
* @param template
* @param factory {@link ObjectFactory} used to create different types of elements to store in the list.
* @param template {@link RedisTemplate} used to perform operations on Redis.
*/
@SuppressWarnings("rawtypes")
AbstractRedisListIntegrationTests(ObjectFactory<T> factory, RedisTemplate template) {
@@ -61,11 +63,12 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
public void setUp() throws Exception {
super.setUp();
list = (RedisList<T>) collection;
this.list = (RedisList<T>) this.collection;
}
@ParameterizedRedisTest
void testAddIndexObjectHead() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -80,6 +83,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testAddIndexObjectTail() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -94,6 +98,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testAddIndexObjectMiddle() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -107,6 +112,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void addAllIndexCollectionHead() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -118,7 +124,9 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
List<T> asList = Arrays.asList(t3, t4);
assertThat(list.get(0)).isEqualTo(t1);
list.addAll(0, asList);
// verify insertion order
assertThat(list.get(0)).isEqualTo(t3);
assertThat(list.get(1)).isEqualTo(t4);
@@ -126,6 +134,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void addAllIndexCollectionTail() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -146,6 +155,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void addAllIndexCollectionMiddle() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -164,8 +174,6 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@EnabledOnCommand("LPOS")
void testIndexOfObject() {
assumeThat(template.getConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
T t1 = getT();
T t2 = getT();
@@ -180,6 +188,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testOffer() {
T t1 = getT();
assertThat(list.offer(t1)).isTrue();
@@ -188,11 +197,17 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testPeek() {
assertThat(list.peek()).isNull();
T t1 = getT();
list.add(t1);
assertThat(list.peek()).isEqualTo(t1);
list.clear();
assertThat(list.peek()).isNull();
}
@@ -202,8 +217,10 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(list::element);
T t1 = getT();
list.add(t1);
assertThat(list.element()).isEqualTo(t1);
list.clear();
assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(list::element);
}
@@ -215,9 +232,13 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testPoll() {
assertThat(list.poll()).isNull();
T t1 = getT();
list.add(t1);
assertThat(list.poll()).isEqualTo(t1);
assertThat(list.poll()).isNull();
}
@@ -232,16 +253,20 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testRemove() {
assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(list::remove);
T t1 = getT();
list.add(t1);
assertThat(list.remove()).isEqualTo(t1);
assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(list::remove);
}
@ParameterizedRedisTest // GH-2039
@EnabledOnCommand("LMOVE")
@SuppressWarnings("unchecked")
void testMoveFirstTo() {
RedisList<T> target = new DefaultRedisList<T>(template.boundListOps(collection.getKey() + ":target"));
@@ -263,6 +288,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest // GH-2039
@EnabledOnCommand("LMOVE")
@SuppressWarnings("unchecked")
void testMoveLastTo() {
RedisList<T> target = new DefaultRedisList<T>(template.boundListOps(collection.getKey() + ":target"));
@@ -284,12 +310,15 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testRange() {
T t1 = getT();
T t2 = getT();
assertThat(list.range(0, -1)).isEmpty();
list.add(t1);
list.add(t2);
assertThat(list.range(0, -1)).hasSize(2);
assertThat(list.range(0, 0).get(0)).isEqualTo(t1);
assertThat(list.range(1, 1).get(0)).isEqualTo(t2);
@@ -302,22 +331,28 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testSet() {
T t1 = getT();
T t2 = getT();
list.add(t1);
list.set(0, t1);
assertThat(list.set(0, t2)).isEqualTo(t1);
assertThat(list.get(0)).isEqualTo(t2);
}
@ParameterizedRedisTest
void testTrim() {
T t1 = getT();
T t2 = getT();
assertThat(list.trim(0, 0)).isEmpty();
list.add(t1);
list.add(t2);
assertThat(list).hasSize(2);
assertThat(list.trim(0L, 0L)).hasSize(1);
assertThat(list).hasSize(1);
@@ -327,14 +362,22 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@SuppressWarnings("unchecked")
@ParameterizedRedisTest
void testCappedCollection() throws Exception {
void testCappedCollection() {
RedisList<T> cappedList = new DefaultRedisList<T>(template.boundListOps(collection.getKey() + ":capped"), 1);
T first = getT();
cappedList.offer(first);
assertThat(cappedList).hasSize(1);
cappedList.add(getT());
assertThat(cappedList).hasSize(1);
T last = getT();
cappedList.add(last);
assertThat(cappedList).hasSize(1);
assertThat(cappedList.get(0)).isEqualTo(first);
@@ -342,15 +385,15 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testAddFirst() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
list.addFirst(t1);
list.addFirst(t2);
list.addFirst(t3);
Arrays.asList(t1, t2, t3).forEach(this.list::addFirst);
Iterator<T> iterator = list.iterator();
assertThat(iterator.next()).isEqualTo(t3);
assertThat(iterator.next()).isEqualTo(t2);
assertThat(iterator.next()).isEqualTo(t1);
@@ -358,11 +401,23 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testAddLast() {
testAdd();
T t1 = getT();
T t2 = getT();
T t3 = getT();
Arrays.asList(t1, t2, t3).forEach(this.list::addLast);
Iterator<T> iterator = list.iterator();
assertThat(iterator.next()).isEqualTo(t1);
assertThat(iterator.next()).isEqualTo(t2);
assertThat(iterator.next()).isEqualTo(t3);
}
@ParameterizedRedisTest
void testDescendingIterator() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -372,14 +427,70 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
list.add(t3);
Iterator<T> iterator = list.descendingIterator();
assertThat(iterator.next()).isEqualTo(t3);
assertThat(iterator.next()).isEqualTo(t2);
assertThat(iterator.next()).isEqualTo(t1);
}
@ParameterizedRedisTest // GH-2602
void testListIteratorAddNextPreviousIsCorrect() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
ListIterator<T> listIterator = this.list.listIterator();
assertThat(listIterator).isNotNull();
Arrays.asList(t1, t2, t3).forEach(listIterator::add);
assertThat(this.list).containsExactly(t1, t2, t3);
assertThat(listIterator.hasNext()).isFalse();
assertThat(listIterator.hasPrevious()).isTrue();
assertThat(listIterator.previous()).isEqualTo(t3);
assertThat(listIterator.previous()).isEqualTo(t2);
assertThat(listIterator.previous()).isEqualTo(t1);
assertThat(listIterator.hasPrevious()).isFalse();
assertThat(listIterator.hasNext()).isTrue();
assertThat(listIterator.next()).isEqualTo(t1);
assertThat(listIterator.next()).isEqualTo(t2);
assertThat(listIterator.next()).isEqualTo(t3);
assertThat(listIterator.hasNext()).isFalse();
assertThat(listIterator.hasPrevious()).isTrue();
}
@ParameterizedRedisTest // GH-2602
public void testListIteratorSetIsCorrect() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
T t4 = getT();
T t5 = getT();
Collections.addAll(this.list, t1, t2, t3, t2, t5);
assertThat(this.list).containsExactly(t1, t2, t3, t2, t5);
ListIterator<T> listIterator = this.list.listIterator();
int index = 0;
while (listIterator.hasNext()) {
listIterator.next();
if (index++ == 3) {
listIterator.set(t4);
}
}
assertThat(this.list).containsExactly(t1, t2, t3, t4, t5);
}
@ParameterizedRedisTest
void testDrainToCollectionWithMaxElements() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -391,12 +502,14 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
List<T> c = new ArrayList<>();
list.drainTo(c, 2);
assertThat(list).hasSize(1).contains(t3);
assertThat(c).hasSize(2).contains(t1, t2);
}
@ParameterizedRedisTest
void testDrainToCollection() {
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -408,12 +521,14 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
List<T> c = new ArrayList<>();
list.drainTo(c);
assertThat(list).isEmpty();
assertThat(c).hasSize(3).contains(t1, t2, t3);
}
@ParameterizedRedisTest
void testGetFirst() {
T t1 = getT();
T t2 = getT();
@@ -445,6 +560,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testPeekLast() {
T t1 = getT();
T t2 = getT();
@@ -462,6 +578,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@ParameterizedRedisTest
void testPollLast() {
T t1 = getT();
T t2 = getT();
@@ -469,6 +586,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
list.add(t2);
T last = list.pollLast();
assertThat(last).isEqualTo(t2);
assertThat(list).hasSize(1).contains(t1);
}
@@ -483,6 +601,7 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
list.add(t2);
T last = list.pollLast(1, TimeUnit.MILLISECONDS);
assertThat(last).isEqualTo(t2);
assertThat(list).hasSize(1).contains(t1);
}
@@ -532,8 +651,8 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
list.add(t2);
list.add(t1);
list.add(t2);
list.removeLastOccurrence(t2);
assertThat(list).hasSize(3).containsExactly(t1, t2, t1);
}
@@ -556,8 +675,6 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
@EnabledOnCommand("LPOS")
void lastIndexOf() {
assumeThat(template.getConnectionFactory()).isInstanceOf(LettuceConnectionFactory.class);
T t1 = getT();
T t2 = getT();
T t3 = getT();
@@ -569,4 +686,163 @@ public abstract class AbstractRedisListIntegrationTests<T> extends AbstractRedis
assertThat(list.lastIndexOf(t1)).isEqualTo(2);
}
@ParameterizedRedisTest // GH-2602
void testReversed() {
T elementOne = getT();
T elementTwo = getT();
T elementThree = getT();
Collections.addAll(this.list, elementOne, elementTwo, elementThree);
assertThat(this.list).containsExactly(elementOne, elementTwo, elementThree);
RedisList<T> reversedList = this.list.reversed();
assertThat(reversedList).isNotNull();
assertThat(reversedList).isNotSameAs(this.list);
assertThat(reversedList).hasSameSizeAs(this.list);
assertThat(reversedList).containsExactly(elementThree, elementTwo, elementOne);
assertThat(reversedList.reversed()).isEqualTo(this.list);
}
@ParameterizedRedisTest // // GH-2602
public void testReversedListIterator() {
T elementOne = getT();
T elementTwo = getT();
T elementThree = getT();
T elementFour = getT();
Collections.addAll(this.list, elementOne, elementTwo, elementThree, elementFour);
assertThat(this.list).containsExactly(elementOne, elementTwo, elementThree, elementFour);
List<T> expectedList = Arrays.asList(elementFour, elementThree, elementTwo, elementOne);
RedisList<T> reversedList = this.list.reversed();
assertThat(reversedList).containsExactly(elementFour, elementThree, elementTwo, elementOne);
Iterator<T> reversedListIterator = reversedList.iterator();
assertThat(reversedListIterator).isNotNull();
assertThat(reversedListIterator).hasNext();
int index = -1;
while (reversedListIterator.hasNext()) {
assertThat(reversedListIterator.next()).isEqualTo(expectedList.get(++index));
if (index == 1) {
reversedListIterator.remove();
++index;
}
}
assertThat(reversedList).containsExactly(elementFour, elementTwo, elementOne);
RedisList<T> reorderedList = reversedList.reversed();
assertThat(reorderedList).isEqualTo(this.list);
assertThat(reorderedList).hasSameSizeAs(reversedList);
assertThat(reorderedList).containsExactly(elementOne, elementTwo, elementFour);
}
@ParameterizedRedisTest // GH-2602
void testReversedWithAddFirst() {
T elementOne = getT();
T elementTwo = getT();
T elementThree = getT();
Collections.addAll(this.list, elementOne, elementTwo);
RedisList<T> reversedList = this.list.reversed();
reversedList.addFirst(elementThree);
assertThat(reversedList).containsExactly(elementThree, elementTwo, elementOne);
RedisList<T> reorderedList = reversedList.reversed();
assertThat(reorderedList).containsExactly(elementOne, elementTwo, elementThree);
}
@ParameterizedRedisTest // GH-2602
void testReversedWithAddLast() {
T elementZero = getT();
T elementOne = getT();
T elementTwo = getT();
Collections.addAll(this.list, elementOne, elementTwo);
assertThat(this.list).containsExactly(elementOne, elementTwo);
RedisList<T> reversedList = this.list.reversed();
reversedList.addLast(elementZero);
assertThat(reversedList).containsExactly(elementTwo, elementOne, elementZero);
RedisList<T> reorderedList = reversedList.reversed();
assertThat(reorderedList).containsExactly(elementZero, elementOne, elementTwo);
}
@ParameterizedRedisTest // GH-2602
void testReversedWithRemoveFirst() {
T elementOne = getT();
T elementTwo = getT();
T elementThree = getT();
Collections.addAll(this.list, elementOne, elementTwo, elementThree);
RedisList<T> reversedList = this.list.reversed();
assertThat(reversedList).containsExactly(elementThree, elementTwo, elementOne);
assertThat(reversedList.removeFirst()).isEqualTo(elementThree);
assertThat(reversedList).containsExactly(elementTwo, elementOne);
RedisList<T> reorderedList = reversedList.reversed();
assertThat(reorderedList).containsExactly(elementOne, elementTwo);
}
@ParameterizedRedisTest // GH-2602
void testReversedWithRemoveLast() {
T elementOne = getT();
T elementTwo = getT();
T elementThree = getT();
Collections.addAll(this.list, elementOne, elementTwo, elementThree);
RedisList<T> reversedList = this.list.reversed();
assertThat(reversedList).containsExactly(elementThree, elementTwo, elementOne);
assertThat(reversedList.removeLast()).isEqualTo(elementOne);
assertThat(reversedList).containsExactly(elementThree, elementTwo);
RedisList<T> reorderedList = reversedList.reversed();
assertThat(reorderedList).containsExactly(elementTwo, elementThree);
}
@SuppressWarnings("unused")
private static String toString(List<?> list) {
StringBuilder stringBuilder = new StringBuilder("[");
boolean comma = false;
for (Object element : list) {
stringBuilder.append(comma ? ", " : "").append(element);
comma = true;
}
return stringBuilder.append("]").toString();
}
}

View File

@@ -22,14 +22,15 @@ import org.springframework.data.redis.core.RedisTemplate;
* Parameterized instance of Redis tests.
*
* @author Costin Leau
* @author John Blum
*/
public class RedisListIntegrationTests extends AbstractRedisListIntegrationTests<Object> {
/**
* Constructs a new <code>RedisListTests</code> instance.
* Constructs a new, parameterized {@link RedisListIntegrationTests}.
*
* @param factory
* @param connFactory
* @param factory {@link ObjectFactory} used to create different types of elements to store in the list.
* @param template {@link RedisTemplate} used to perform operations on Redis.
*/
public RedisListIntegrationTests(ObjectFactory<Object> factory, RedisTemplate<Object, Object> template) {
super(factory, template);
@@ -40,7 +41,6 @@ public class RedisListIntegrationTests extends AbstractRedisListIntegrationTests
}
AbstractRedisCollection<Object> createCollection() {
String redisName = getClass().getName();
return new DefaultRedisList<>(redisName, template);
return new DefaultRedisList<Object>(getClass().getName(), this.template);
}
}