/*
* Copyright 2011-2013 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
*
* http://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.Collection;
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.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisOperations;
/**
* Default implementation for {@link RedisList}. Suitable for not just lists, but also queues (FIFO ordering) or stacks
* (LIFO ordering) and deques (or double ended queues). Allows the maximum size (or the cap) to be specified to prevent
* the list from over growing. Note that all write operations will execute immediately, whether a cap is specified or
* not - the list will always accept new items (trimming the tail after each insert in case of capped collections).
*
* @author Costin Leau
*/
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> {
public DefaultRedisListIterator(Iterator<E> delegate) {
super(delegate);
}
protected void removeFromRedisStorage(E item) {
DefaultRedisList.this.remove(item);
}
}
/**
* Constructs a new, uncapped <code>DefaultRedisList</code> instance.
*
* @param key
* @param operations
*/
public DefaultRedisList(String key, RedisOperations<String, E> operations) {
this(operations.boundListOps(key));
}
/**
* Constructs a new, uncapped <code>DefaultRedisList</code> instance.
*
* @param boundOps
*/
public DefaultRedisList(BoundListOperations<String, E> boundOps) {
this(boundOps, 0);
}
/**
* Constructs a new <code>DefaultRedisList</code> instance.
*
* @param boundOps
* @param maxSize
*/
public DefaultRedisList(BoundListOperations<String, E> boundOps, int maxSize) {
super(boundOps.getKey(), boundOps.getOperations());
listOps = boundOps;
setMaxSize(maxSize);
}
/**
* Sets the maximum size of the (capped) list. A value of 0 means unlimited.
*
* @param maxSize list maximum size
*/
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
capped = (maxSize > 0);
}
public List<E> range(long start, long end) {
return listOps.range(start, end);
}
public RedisList<E> trim(int start, int end) {
listOps.trim(start, end);
return this;
}
private List<E> content() {
return listOps.range(0, -1);
}
private void cap() {
if (capped) {
listOps.trim(0, maxSize - 1);
}
}
public Iterator<E> iterator() {
List<E> list = content();
checkResult(list);
return new DefaultRedisListIterator(list.iterator());
}
public int size() {
Long size = listOps.size();
checkResult(size);
return size.intValue();
}
public boolean add(E value) {
listOps.rightPush(value);
cap();
return true;
}
public void clear() {
listOps.trim(size() + 1, 0);
}
public boolean remove(Object o) {
Long result = listOps.remove(1, o);
return (result != null && result.longValue() > 0);
}
public void add(int index, E element) {
if (index == 0) {
listOps.leftPush(element);
cap();
return;
}
int size = size();
if (index == size()) {
listOps.rightPush(element);
cap();
return;
}
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException();
}
throw new IllegalArgumentException("Redis supports insertion only at the beginning or the end of the list");
}
public boolean addAll(int index, Collection<? extends E> c) {
// insert collection in reverse
if (index == 0) {
Collection<? extends E> reverseC = CollectionUtils.reverse(c);
for (E e : reverseC) {
listOps.leftPush(e);
cap();
}
return true;
}
int size = size();
if (index == size()) {
for (E e : c) {
listOps.rightPush(e);
cap();
}
return true;
}
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException();
}
throw new IllegalArgumentException("Redis supports insertion only at the beginning or the end of the list");
}
public E get(int index) {
if (index < 0 || index > size()) {
throw new IndexOutOfBoundsException();
}
return listOps.index(index);
}
public int indexOf(Object o) {
throw new UnsupportedOperationException();
}
public int lastIndexOf(Object o) {
throw new UnsupportedOperationException();
}
public ListIterator<E> listIterator() {
throw new UnsupportedOperationException();
}
public ListIterator<E> listIterator(int index) {
throw new UnsupportedOperationException();
}
public E remove(int index) {
throw new UnsupportedOperationException();
}
public E set(int index, E e) {
E object = get(index);
listOps.set(index, e);
return object;
}
public List<E> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
//
// Queue methods
//
public E element() {
E value = peek();
if (value == null)
throw new NoSuchElementException();
return value;
}
public boolean offer(E e) {
listOps.rightPush(e);
cap();
return true;
}
public E peek() {
return listOps.index(0);
}
public E poll() {
return listOps.leftPop();
}
public E remove() {
E value = poll();
if (value == null)
throw new NoSuchElementException();
return value;
}
//
// Dequeue
//
public void addFirst(E e) {
listOps.leftPush(e);
cap();
}
public void addLast(E e) {
add(e);
}
public Iterator<E> descendingIterator() {
List<E> content = content();
Collections.reverse(content);
return new DefaultRedisListIterator(content.iterator());
}
public E getFirst() {
return element();
}
public E getLast() {
E e = peekLast();
if (e == null) {
throw new NoSuchElementException();
}
return e;
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
public E peekFirst() {
return peek();
}
public E peekLast() {
return listOps.index(-1);
}
public E pollFirst() {
return poll();
}
public E pollLast() {
return listOps.rightPop();
}
public E pop() {
E e = poll();
if (e == null) {
throw new NoSuchElementException();
}
return e;
}
public void push(E e) {
addFirst(e);
}
public E removeFirst() {
return pop();
}
public boolean removeFirstOccurrence(Object o) {
return remove(o);
}
public E removeLast() {
E e = pollLast();
if (e == null) {
throw new NoSuchElementException();
}
return e;
}
public boolean removeLastOccurrence(Object o) {
Long result = listOps.remove(-1, o);
return (result != null && result.longValue() > 0);
}
//
// BlockingQueue
//
public int drainTo(Collection<? super E> c, int maxElements) {
if (this.equals(c)) {
throw new IllegalArgumentException("Cannot drain a queue to itself");
}
int size = size();
int loop = (size >= maxElements ? maxElements : size);
for (int index = 0; index < loop; index++) {
c.add(poll());
}
return loop;
}
public int drainTo(Collection<? super E> c) {
return drainTo(c, size());
}
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
return offer(e);
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E element = listOps.leftPop(timeout, unit);
return (element == null ? null : element);
}
public void put(E e) throws InterruptedException {
offer(e);
}
public int remainingCapacity() {
return Integer.MAX_VALUE;
}
public E take() throws InterruptedException {
return poll(0, TimeUnit.SECONDS);
}
//
// BlockingDeque
//
public boolean offerFirst(E e, long timeout, TimeUnit unit) throws InterruptedException {
return offerFirst(e);
}
public boolean offerLast(E e, long timeout, TimeUnit unit) throws InterruptedException {
return offerLast(e);
}
public E pollFirst(long timeout, TimeUnit unit) throws InterruptedException {
return poll(timeout, unit);
}
public E pollLast(long timeout, TimeUnit unit) throws InterruptedException {
E element = listOps.rightPop(timeout, unit);
return (element == null ? null : element);
}
public void putFirst(E e) throws InterruptedException {
add(e);
}
public void putLast(E e) throws InterruptedException {
put(e);
}
public E takeFirst() throws InterruptedException {
return take();
}
public E takeLast() throws InterruptedException {
return pollLast(0, TimeUnit.SECONDS);
}
public DataType getType() {
return DataType.LIST;
}
}