Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.redisson.RedissonPriorityQueue Maven / Gradle / Ivy
/**
* Copyright 2018 Nikita Koksharov
*
* 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.redisson;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.redisson.api.RBucket;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.api.RPriorityQueue;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
/**
*
* @author Nikita Koksharov
*
* @param value type
*/
public class RedissonPriorityQueue extends RedissonList implements RPriorityQueue {
private static class NaturalComparator implements Comparator, Serializable {
private static final long serialVersionUID = 7207038068494060240L;
static final NaturalComparator NATURAL_ORDER = new NaturalComparator();
public int compare(V c1, V c2) {
Comparable c1co = (Comparable) c1;
Comparable c2co = (Comparable) c2;
return c1co.compareTo(c2co);
}
}
public static class BinarySearchResult {
private V value;
private Integer index;
public BinarySearchResult(V value) {
super();
this.value = value;
}
public BinarySearchResult() {
}
public void setIndex(Integer index) {
this.index = index;
}
public Integer getIndex() {
return index;
}
public V getValue() {
return value;
}
}
private Comparator super V> comparator = NaturalComparator.NATURAL_ORDER;
CommandExecutor commandExecutor;
RLock lock;
private RBucket comparatorHolder;
public RedissonPriorityQueue(CommandExecutor commandExecutor, String name, RedissonClient redisson) {
super(commandExecutor, name, redisson);
this.commandExecutor = commandExecutor;
comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE);
lock = redisson.getLock("redisson_sortedset_lock:{" + getName() + "}");
loadComparator();
}
public RedissonPriorityQueue(Codec codec, CommandExecutor commandExecutor, String name, RedissonClient redisson) {
super(codec, commandExecutor, name, redisson);
this.commandExecutor = commandExecutor;
comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE);
lock = redisson.getLock("redisson_sortedset_lock:{" + getName() + "}");
loadComparator();
}
private void loadComparator() {
try {
String comparatorSign = comparatorHolder.get();
if (comparatorSign != null) {
String[] parts = comparatorSign.split(":");
String className = parts[0];
String sign = parts[1];
String result = calcClassSign(className);
if (!result.equals(sign)) {
throw new IllegalStateException("Local class signature of " + className + " differs from used by this SortedSet!");
}
Class> clazz = Class.forName(className);
comparator = (Comparator) clazz.newInstance();
}
} catch (IllegalStateException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// TODO cache result
private static String calcClassSign(String name) {
try {
Class> clazz = Class.forName(name);
ByteArrayOutputStream result = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(result);
outputStream.writeObject(clazz);
outputStream.close();
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(result.toByteArray());
return new BigInteger(1, crypt.digest()).toString(16);
} catch (Exception e) {
throw new IllegalStateException("Can't calculate sign of " + name, e);
}
}
@Override
public boolean offer(V e) {
return add(e);
}
@Override
public boolean contains(final Object o) {
return binarySearch((V)o, codec).getIndex() >= 0;
}
@Override
public boolean add(V value) {
lock.lock();
try {
checkComparator();
BinarySearchResult res = binarySearch(value, codec);
int index = 0;
if (res.getIndex() < 0) {
index = -(res.getIndex() + 1);
} else {
index = res.getIndex() + 1;
}
commandExecutor.evalWrite(getName(), RedisCommands.EVAL_VOID,
"local len = redis.call('llen', KEYS[1]);"
+ "if tonumber(ARGV[1]) < len then "
+ "local pivot = redis.call('lindex', KEYS[1], ARGV[1]);"
+ "redis.call('linsert', KEYS[1], 'before', pivot, ARGV[2]);"
+ "return;"
+ "end;"
+ "redis.call('rpush', KEYS[1], ARGV[2]);",
Arrays.asList(getName()),
index, encode(value));
return true;
} finally {
lock.unlock();
}
}
private void checkComparator() {
String comparatorSign = comparatorHolder.get();
if (comparatorSign != null) {
String[] vals = comparatorSign.split(":");
String className = vals[0];
if (!comparator.getClass().getName().equals(className)) {
loadComparator();
}
}
}
@Override
public boolean remove(Object value) {
lock.lock();
try {
checkComparator();
BinarySearchResult res = binarySearch((V) value, codec);
if (res.getIndex() < 0) {
return false;
}
remove((int)res.getIndex());
return true;
} finally {
lock.unlock();
}
}
@Override
public boolean containsAll(Collection> c) {
for (Object object : c) {
if (!contains(object)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection extends V> c) {
boolean changed = false;
for (V v : c) {
if (add(v)) {
changed = true;
}
}
return changed;
}
@Override
public boolean retainAll(Collection> c) {
boolean changed = false;
for (Iterator> iterator = iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
if (!c.contains(object)) {
iterator.remove();
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection> c) {
boolean changed = false;
for (Object obj : c) {
if (remove(obj)) {
changed = true;
}
}
return changed;
}
@Override
public void clear() {
delete();
}
@Override
public Comparator super V> comparator() {
return comparator;
}
public RFuture pollAsync() {
return pollAsync(RedisCommands.LPOP, getName());
}
protected RFuture pollAsync(final RedisCommand command, final Object ... params) {
final long threadId = Thread.currentThread().getId();
final RPromise result = new RedissonPromise();
lock.lockAsync(threadId).addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
RFuture f = commandExecutor.writeAsync(getName(), codec, command, params);
f.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
final V value = future.getNow();
lock.unlockAsync(threadId).addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
result.tryFailure(future.cause());
return;
}
result.trySuccess(value);
}
});
}
});
}
});
return result;
}
public V getFirst() {
V value = getValue(0);
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
@Override
public V poll() {
return get(pollAsync());
}
@Override
public V element() {
return getFirst();
}
// @Override
public RFuture peekAsync() {
return getAsync(0);
}
@Override
public V peek() {
return getValue(0);
}
private String getComparatorKeyName() {
return suffixName(getName(), "redisson_sortedset_comparator");
}
@Override
public boolean trySetComparator(Comparator super V> comparator) {
String className = comparator.getClass().getName();
final String comparatorSign = className + ":" + calcClassSign(className);
Boolean res = commandExecutor.evalWrite(getName(), StringCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if redis.call('llen', KEYS[1]) == 0 then "
+ "redis.call('set', KEYS[2], ARGV[1]); "
+ "return 1; "
+ "else "
+ "return 0; "
+ "end",
Arrays.asList(getName(), getComparatorKeyName()), comparatorSign);
if (res) {
this.comparator = comparator;
}
return res;
}
@Override
public V remove() {
return removeFirst();
}
public V removeFirst() {
V value = poll();
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
// TODO optimize: get three values each time instead of single
public BinarySearchResult binarySearch(V value, Codec codec) {
int size = size();
int upperIndex = size - 1;
int lowerIndex = 0;
while (lowerIndex <= upperIndex) {
int index = lowerIndex + (upperIndex - lowerIndex) / 2;
V res = getValue(index);
if (res == null) {
return new BinarySearchResult();
}
int cmp = comparator.compare(value, res);
if (cmp == 0) {
BinarySearchResult indexRes = new BinarySearchResult();
indexRes.setIndex(index);
return indexRes;
} else if (cmp < 0) {
upperIndex = index - 1;
} else {
lowerIndex = index + 1;
}
}
BinarySearchResult indexRes = new BinarySearchResult();
indexRes.setIndex(-(lowerIndex + 1));
return indexRes;
}
public String toString() {
Iterator it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
V e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
@Override
public V pollLastAndOfferFirstTo(String queueName) {
return get(pollLastAndOfferFirstToAsync(queueName));
}
public RFuture pollLastAndOfferFirstToAsync(String queueName) {
return pollAsync(RedisCommands.RPOPLPUSH, getName(), queueName);
}
}