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.
redis.server.netty.SimpleRedisServer Maven / Gradle / Ivy
package redis.server.netty;
import io.netty.buffer.ByteBuf;
import redis.netty4.*;
import redis.util.*;
import java.lang.reflect.Field;
import java.security.SecureRandom;
import java.util.*;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.MAX_VALUE;
import static redis.netty4.BulkReply.NIL_REPLY;
import static redis.netty4.IntegerReply.integer;
import static redis.netty4.StatusReply.OK;
import static redis.netty4.StatusReply.QUIT;
import static redis.util.Encoding.bytesToNum;
import static redis.util.Encoding.numToBytes;
public class SimpleRedisServer implements RedisServer {
private static final StatusReply PONG = new StatusReply("PONG");
private long started = now();
private BytesKeyObjectMap data = new BytesKeyObjectMap();
private BytesKeyObjectMap expires = new BytesKeyObjectMap();
private static int[] mask = {128, 64, 32, 16, 8, 4, 2, 1};
private static RedisException invalidValue() {
return new RedisException("Operation against a key holding the wrong kind of value");
}
private static RedisException notInteger() {
return new RedisException("value is not an integer or out of range");
}
private static RedisException notFloat() {
return new RedisException("value is not a float or out of range");
}
@SuppressWarnings("unchecked")
private BytesKeyObjectMap _gethash(byte[] key0, boolean create) throws RedisException {
Object o = _get(key0);
if (o == null) {
o = new BytesKeyObjectMap();
if (create) {
data.put(key0, o);
}
}
if (!(o instanceof HashMap)) {
throw invalidValue();
}
return (BytesKeyObjectMap) o;
}
@SuppressWarnings("unchecked")
private BytesKeySet _getset(byte[] key0, boolean create) throws RedisException {
Object o = _get(key0);
if (o == null) {
o = new BytesKeySet();
if (create) {
data.put(key0, o);
}
}
if (!(o instanceof BytesKeySet)) {
throw invalidValue();
}
return (BytesKeySet) o;
}
@SuppressWarnings("unchecked")
private ZSet _getzset(byte[] key0, boolean create) throws RedisException {
Object o = _get(key0);
if (o == null) {
o = new ZSet();
if (create) {
data.put(key0, o);
}
}
if (!(o instanceof ZSet)) {
throw invalidValue();
}
return (ZSet) o;
}
private Object _get(byte[] key0) {
Object o = data.get(key0);
if (o != null) {
Long l = expires.get(key0);
if (l != null) {
if (l < now()) {
data.remove(key0);
return null;
}
}
}
return o;
}
private IntegerReply _change(byte[] key0, long delta) throws RedisException {
Object o = _get(key0);
if (o == null) {
_put(key0, numToBytes(delta, false));
return integer(delta);
} else if (o instanceof byte[]) {
try {
long integer = bytesToNum((byte[]) o) + delta;
_put(key0, numToBytes(integer, false));
return integer(integer);
} catch (IllegalArgumentException e) {
throw new RedisException(e.getMessage());
}
} else {
throw notInteger();
}
}
private BulkReply _change(byte[] key0, double delta) throws RedisException {
Object o = _get(key0);
if (o == null) {
byte[] bytes = _tobytes(delta);
_put(key0, bytes);
return new BulkReply(bytes);
} else if (o instanceof byte[]) {
try {
double number = _todouble((byte[]) o) + delta;
byte[] bytes = _tobytes(number);
_put(key0, bytes);
return new BulkReply(bytes);
} catch (IllegalArgumentException e) {
throw new RedisException(e.getMessage());
}
} else {
throw notInteger();
}
}
private static int _test(byte[] bytes, long offset) throws RedisException {
long div = offset / 8;
if (div > MAX_VALUE) throw notInteger();
int i;
if (bytes.length < div + 1) {
i = 0;
} else {
int mod = (int) (offset % 8);
int value = bytes[((int) div)] & 0xFF;
i = value & mask[mod];
}
return i != 0 ? 1 : 0;
}
private byte[] _getbytes(byte[] aKey2) throws RedisException {
byte[] src;
Object o = _get(aKey2);
if (o instanceof byte[]) {
src = (byte[]) o;
} else if (o != null) {
throw invalidValue();
} else {
src = new byte[0];
}
return src;
}
@SuppressWarnings("unchecked")
private List _getlist(byte[] key0, boolean create) throws RedisException {
Object o = _get(key0);
if (o instanceof List) {
return (List) o;
} else if (o == null) {
if (create) {
ArrayList list = new ArrayList();
_put(key0, list);
return list;
} else {
return null;
}
} else {
throw invalidValue();
}
}
private Object _put(byte[] key, Object value) {
expires.remove(key);
return data.put(key, value);
}
private Object _put(byte[] key, byte[] value, long expiration) {
expires.put(key, expiration);
return data.put(key, value);
}
private static boolean matches(byte[] key, byte[] pattern, int kp, int pp) {
if (kp == key.length) {
return pp == pattern.length || (pp == pattern.length - 1 && pattern[pp] == '*');
} else if (pp == pattern.length) {
return false;
}
byte c = key[kp];
byte p = pattern[pp];
switch (p) {
case '?':
// Always matches, move to next character in key and pattern
return matches(key, pattern, kp + 1, pp + 1);
case '*':
// Matches this character than either matches end or try next character
return matches(key, pattern, kp + 1, pp + 1) || matches(key, pattern, kp + 1, pp);
case '\\':
// Matches the escaped character and the rest
return c == pattern[pp + 1] && matches(key, pattern, kp + 1, pp + 2);
case '[':
// Matches one of the characters and the rest
boolean found = false;
pp++;
do {
byte b = pattern[pp++];
if (b == ']') {
break;
} else {
if (b == c) found = true;
}
} while (true);
return found && matches(key, pattern, kp + 1, pp);
default:
// This matches and the rest
return c == p && matches(key, pattern, kp + 1, pp + 1);
}
}
private static int _toposint(byte[] offset1) throws RedisException {
long offset = bytesToNum(offset1);
if (offset < 0 || offset > MAX_VALUE) {
throw notInteger();
}
return (int) offset;
}
private static int _toint(byte[] offset1) throws RedisException {
long offset = bytesToNum(offset1);
if (offset > MAX_VALUE) {
throw notInteger();
}
return (int) offset;
}
private static int _torange(byte[] offset1, int length) throws RedisException {
long offset = bytesToNum(offset1);
if (offset > MAX_VALUE) {
throw notInteger();
}
if (offset < 0) {
offset = (length + offset);
}
if (offset >= length) {
offset = length - 1;
}
return (int) offset;
}
private static Random r = new SecureRandom();
private static Field tableField;
private static Field nextField;
private static Field mapField;
static {
try {
tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
nextField = Class.forName("java.util.HashMap$Entry").getDeclaredField("next");
nextField.setAccessible(true);
mapField = HashSet.class.getDeclaredField("map");
mapField.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
tableField = null;
nextField = null;
}
}
private static RedisException noSuchKey() {
return new RedisException("no such key");
}
private long now() {
return System.currentTimeMillis();
}
/**
* Append a value to a key
* String
*
* @param key0
* @param value1
* @return IntegerReply
*/
@Override
public IntegerReply append(byte[] key0, byte[] value1) throws RedisException {
Object o = _get(key0);
int length1 = value1.length;
if (o instanceof byte[]) {
int length0 = ((byte[]) o).length;
byte[] bytes = new byte[length0 + length1];
System.arraycopy(o, 0, bytes, 0, length0);
System.arraycopy(value1, 0, bytes, length0, length1);
_put(key0, bytes);
return integer(bytes.length);
} else if (o == null) {
_put(key0, value1);
return integer(length1);
} else {
throw invalidValue();
}
}
/**
* Count set bits in a string
* String
*
* @param key0
* @param start1
* @param end2
* @return IntegerReply
*/
@Override
public IntegerReply bitcount(byte[] key0, byte[] start1, byte[] end2) throws RedisException {
Object o = _get(key0);
if (o instanceof byte[]) {
byte[] bytes = (byte[]) o;
int size = bytes.length;
int s = _torange(start1, size);
int e = _torange(end2, size);
if (e < s) e = s;
int total = 0;
for (int i = (int) s; i <= e; i++) {
int b = bytes[i] & 0xFF;
for (int j = 0; j < 8; j++) {
if ((b & mask[j]) != 0) {
total++;
}
}
}
return integer(total);
} else if (o == null) {
return integer(0);
} else {
throw invalidValue();
}
}
/**
* Perform bitwise operations between strings
* String
*
* @param operation0
* @param destkey1
* @param key2
* @return IntegerReply
*/
@Override
public IntegerReply bitop(byte[] operation0, byte[] destkey1, byte[][] key2) throws RedisException {
BitOp bitOp = BitOp.valueOf(new String(operation0).toUpperCase());
int size = 0;
for (byte[] aKey2 : key2) {
int length = aKey2.length;
if (length > size) {
size = length;
}
}
byte[] bytes = null;
for (byte[] aKey2 : key2) {
byte[] src;
src = _getbytes(aKey2);
if (bytes == null) {
bytes = new byte[size];
if (bitOp == BitOp.NOT) {
if (key2.length > 1) {
throw new RedisException("invalid number of arguments for 'bitop' NOT operation");
}
for (int i = 0; i < src.length; i++) {
bytes[i] = (byte) ~(src[i] & 0xFF);
}
} else {
System.arraycopy(src, 0, bytes, 0, src.length);
}
} else {
for (int i = 0; i < src.length; i++) {
int d = bytes[i] & 0xFF;
int s = src[i] & 0xFF;
switch (bitOp) {
case AND:
bytes[i] = (byte) (d & s);
break;
case OR:
bytes[i] = (byte) (d | s);
break;
case XOR:
bytes[i] = (byte) (d ^ s);
break;
}
}
}
}
_put(destkey1, bytes);
return integer(bytes == null ? 0 : bytes.length);
}
enum BitOp {AND, OR, XOR, NOT}
/**
* Decrement the integer value of a key by one
* String
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply decr(byte[] key0) throws RedisException {
return _change(key0, -1);
}
/**
* Decrement the integer value of a key by the given number
* String
*
* @param key0
* @param decrement1
* @return IntegerReply
*/
@Override
public IntegerReply decrby(byte[] key0, byte[] decrement1) throws RedisException {
return _change(key0, -bytesToNum(decrement1));
}
/**
* Get the value of a key
* String
*
* @param key0
* @return BulkReply
*/
@Override
public BulkReply get(byte[] key0) throws RedisException {
Object o = _get(key0);
if (o instanceof byte[]) {
return new BulkReply((byte[]) o);
}
if (o == null) {
return NIL_REPLY;
} else {
throw invalidValue();
}
}
/**
* Returns the bit value at offset in the string value stored at key
* String
*
* @param key0
* @param offset1
* @return IntegerReply
*/
@Override
public IntegerReply getbit(byte[] key0, byte[] offset1) throws RedisException {
Object o = _get(key0);
if (o instanceof byte[]) {
long offset = bytesToNum(offset1);
byte[] bytes = (byte[]) o;
return _test(bytes, offset) == 1 ? integer(1) : integer(0);
} else if (o == null) {
return integer(0);
} else {
throw invalidValue();
}
}
/**
* Get a substring of the string stored at a key
* String
*
* @param key0
* @param start1
* @param end2
* @return BulkReply
*/
@Override
public BulkReply getrange(byte[] key0, byte[] start1, byte[] end2) throws RedisException {
byte[] bytes = _getbytes(key0);
int size = bytes.length;
int s = _torange(start1, size);
int e = _torange(end2, size);
if (e < s) e = s;
int length = e - s + 1;
byte[] out = new byte[length];
System.arraycopy(bytes, (int) s, out, 0, length);
return new BulkReply(out);
}
/**
* Set the string value of a key and return its old value
* String
*
* @param key0
* @param value1
* @return BulkReply
*/
@Override
public BulkReply getset(byte[] key0, byte[] value1) throws RedisException {
Object put = _put(key0, value1);
if (put == null || put instanceof byte[]) {
return put == null ? NIL_REPLY : new BulkReply((byte[]) put);
} else {
// Put it back
data.put(key0, put);
throw invalidValue();
}
}
/**
* Increment the integer value of a key by one
* String
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply incr(byte[] key0) throws RedisException {
return _change(key0, 1);
}
/**
* Increment the integer value of a key by the given amount
* String
*
* @param key0
* @param increment1
* @return IntegerReply
*/
@Override
public IntegerReply incrby(byte[] key0, byte[] increment1) throws RedisException {
return _change(key0, bytesToNum(increment1));
}
/**
* Increment the float value of a key by the given amount
* String
*
* @param key0
* @param increment1
* @return BulkReply
*/
@Override
public BulkReply incrbyfloat(byte[] key0, byte[] increment1) throws RedisException {
return _change(key0, _todouble(increment1));
}
/**
* Get the values of all the given keys
* String
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply mget(byte[][] key0) throws RedisException {
int length = key0.length;
Reply[] replies = new Reply[length];
for (int i = 0; i < length; i++) {
Object o = _get(key0[i]);
if (o instanceof byte[]) {
replies[i] = new BulkReply((byte[]) o);
} else {
replies[i] = NIL_REPLY;
}
}
return new MultiBulkReply(replies);
}
/**
* Set multiple keys to multiple values
* String
*
* @param key_or_value0
* @return StatusReply
*/
@Override
public StatusReply mset(byte[][] key_or_value0) throws RedisException {
int length = key_or_value0.length;
if (length % 2 != 0) {
throw new RedisException("wrong number of arguments for MSET");
}
for (int i = 0; i < length; i += 2) {
_put(key_or_value0[i], key_or_value0[i + 1]);
}
return OK;
}
/**
* Set multiple keys to multiple values, only if none of the keys exist
* String
*
* @param key_or_value0
* @return IntegerReply
*/
@Override
public IntegerReply msetnx(byte[][] key_or_value0) throws RedisException {
int length = key_or_value0.length;
if (length % 2 != 0) {
throw new RedisException("wrong number of arguments for MSETNX");
}
for (int i = 0; i < length; i += 2) {
if (_get(key_or_value0[i]) != null) {
return integer(0);
}
}
for (int i = 0; i < length; i += 2) {
_put(key_or_value0[i], key_or_value0[i + 1]);
}
return integer(1);
}
/**
* Set the value and expiration in milliseconds of a key
* String
*
* @param key0
* @param milliseconds1
* @param value2
* @return Reply
*/
@Override
public Reply psetex(byte[] key0, byte[] milliseconds1, byte[] value2) throws RedisException {
_put(key0, value2, bytesToNum(milliseconds1) + now());
return OK;
}
/**
* Set the string value of a key
* String
*
* @param key0
* @param value1
* @return StatusReply
*/
@Override
public StatusReply set(byte[] key0, byte[] value1) throws RedisException {
_put(key0, value1);
return OK;
}
/**
* Sets or clears the bit at offset in the string value stored at key
* String
*
* @param key0
* @param offset1
* @param value2
* @return IntegerReply
*/
@Override
public IntegerReply setbit(byte[] key0, byte[] offset1, byte[] value2) throws RedisException {
int bit = (int) bytesToNum(value2);
if (bit != 0 && bit != 1) throw notInteger();
Object o = _get(key0);
if (o instanceof byte[] || o == null) {
long offset = bytesToNum(offset1);
long div = offset / 8;
if (div + 1 > MAX_VALUE) throw notInteger();
byte[] bytes = (byte[]) o;
if (bytes == null || bytes.length < div + 1) {
byte[] tmp = bytes;
bytes = new byte[(int) div + 1];
if (tmp != null) System.arraycopy(tmp, 0, bytes, 0, tmp.length);
_put(key0, bytes);
}
int mod = (int) (offset % 8);
int value = bytes[((int) div)] & 0xFF;
int i = value & mask[mod];
if (i == 0) {
if (bit != 0) {
bytes[((int) div)] += mask[mod];
}
return integer(0);
} else {
if (bit == 0) {
bytes[((int) div)] -= mask[mod];
}
return integer(1);
}
} else {
throw invalidValue();
}
}
/**
* Set the value and expiration of a key
* String
*
* @param key0
* @param seconds1
* @param value2
* @return StatusReply
*/
@Override
public StatusReply setex(byte[] key0, byte[] seconds1, byte[] value2) throws RedisException {
_put(key0, value2, bytesToNum(seconds1) * 1000 + now());
return OK;
}
/**
* Set the value of a key, only if the key does not exist
* String
*
* @param key0
* @param value1
* @return IntegerReply
*/
@Override
public IntegerReply setnx(byte[] key0, byte[] value1) throws RedisException {
if (_get(key0) == null) {
_put(key0, value1);
return integer(1);
}
return integer(0);
}
/**
* Overwrite part of a string at key starting at the specified offset
* String
*
* @param key0
* @param offset1
* @param value2
* @return IntegerReply
*/
@Override
public IntegerReply setrange(byte[] key0, byte[] offset1, byte[] value2) throws RedisException {
byte[] bytes = _getbytes(key0);
int offset = _toposint(offset1);
int length = (int) (value2.length + offset);
if (bytes.length < length) {
byte[] tmp = bytes;
bytes = new byte[length];
System.arraycopy(tmp, 0, bytes, 0, offset);
_put(key0, bytes);
}
System.arraycopy(value2, 0, bytes, offset, value2.length);
return integer(bytes.length);
}
/**
* Get the length of the value stored in a key
* String
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply strlen(byte[] key0) throws RedisException {
return integer(_getbytes(key0).length);
}
/**
* Authenticate to the server
* Connection
*
* @param password0
* @return StatusReply
*/
public StatusReply auth(byte[] password0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Echo the given string
* Connection
*
* @param message0
* @return BulkReply
*/
@Override
public BulkReply echo(byte[] message0) throws RedisException {
return new BulkReply(message0);
}
/**
* Ping the server
* Connection
*
* @return StatusReply
*/
@Override
public StatusReply ping() throws RedisException {
return PONG;
}
/**
* Close the connection
* Connection
*
* @return StatusReply
*/
@Override
public StatusReply quit() throws RedisException {
return QUIT;
}
/**
* Change the selected database for the current connection
* Connection
*
* @param index0
* @return StatusReply
*/
@Override
public StatusReply select(byte[] index0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Asynchronously rewrite the append-only file
* Server
*
* @return StatusReply
*/
@Override
public StatusReply bgrewriteaof() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Asynchronously save the dataset to disk
* Server
*
* @return StatusReply
*/
@Override
public StatusReply bgsave() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Kill the connection of a client
* Server
*
* @param ip_port0
* @return Reply
*/
@Override
public Reply client_kill(byte[] ip_port0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Get the list of client connections
* Server
*
* @return Reply
*/
@Override
public Reply client_list() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Get the current connection name
* Server
*
* @return Reply
*/
@Override
public Reply client_getname() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Set the current connection name
* Server
*
* @param connection_name0
* @return Reply
*/
@Override
public Reply client_setname(byte[] connection_name0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Get the value of a configuration parameter
* Server
*
* @param parameter0
* @return Reply
*/
@Override
public Reply config_get(byte[] parameter0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Set a configuration parameter to the given value
* Server
*
* @param parameter0
* @param value1
* @return Reply
*/
@Override
public Reply config_set(byte[] parameter0, byte[] value1) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Reset the stats returned by INFO
* Server
*
* @return Reply
*/
@Override
public Reply config_resetstat() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Return the number of keys in the selected database
* Server
*
* @return IntegerReply
*/
@Override
public IntegerReply dbsize() throws RedisException {
return integer(data.size());
}
/**
* Get debugging information about a key
* Server
*
* @param key0
* @return Reply
*/
@Override
public Reply debug_object(byte[] key0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Make the server crash
* Server
*
* @return Reply
*/
@Override
public Reply debug_segfault() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Remove all keys from all databases
* Server
*
* @return StatusReply
*/
@Override
public StatusReply flushall() throws RedisException {
data.clear();
return OK;
}
/**
* Remove all keys from the current database
* Server
*
* @return StatusReply
*/
@Override
public StatusReply flushdb() throws RedisException {
data.clear();
return OK;
}
/**
* Get information and statistics about the server
* Server
*
* @return BulkReply
*/
@Override
public BulkReply info(byte[] section) throws RedisException {
StringBuilder sb = new StringBuilder();
sb.append("redis_version:2.6.0\n");
sb.append("keys:").append(data.size()).append("\n");
sb.append("uptime:").append(now() - started).append("\n");
return new BulkReply(sb.toString().getBytes());
}
/**
* Get the UNIX time stamp of the last successful save to disk
* Server
*
* @return IntegerReply
*/
@Override
public IntegerReply lastsave() throws RedisException {
return integer(-1);
}
/**
* Listen for all requests received by the server in real time
* Server
*
* @return Reply
*/
@Override
public Reply monitor() throws RedisException {
// TODO: Blocking
return null;
}
/**
* Synchronously save the dataset to disk
* Server
*
* @return Reply
*/
@Override
public Reply save() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Synchronously save the dataset to disk and then shut down the server
* Server
*
* @param NOSAVE0
* @param SAVE1
* @return StatusReply
*/
@Override
public StatusReply shutdown(byte[] NOSAVE0, byte[] SAVE1) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Make the server a slave of another instance, or promote it as master
* Server
*
* @param host0
* @param port1
* @return StatusReply
*/
@Override
public StatusReply slaveof(byte[] host0, byte[] port1) throws RedisException {
// TODO
return null;
}
/**
* Manages the Redis slow queries log
* Server
*
* @param subcommand0
* @param argument1
* @return Reply
*/
@Override
public Reply slowlog(byte[] subcommand0, byte[] argument1) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Internal command used for replication
* Server
*
* @return Reply
*/
@Override
public Reply sync() throws RedisException {
// TODO: Blocking
return null;
}
/**
* Return the current server time
* Server
*
* @return MultiBulkReply
*/
@Override
public MultiBulkReply time() throws RedisException {
long millis = System.currentTimeMillis();
long seconds = millis / 1000;
Reply[] replies = new Reply[]{
new BulkReply(numToBytes(seconds)),
new BulkReply(numToBytes((millis - seconds * 1000) * 1000))
};
return new MultiBulkReply(replies);
}
/**
* Remove and get the first element in a list, or block until one is available
* List
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply blpop(byte[][] key0) throws RedisException {
// TODO: Blocking
return null;
}
/**
* Remove and get the last element in a list, or block until one is available
* List
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply brpop(byte[][] key0) throws RedisException {
// TODO: Blocking
return null;
}
/**
* Pop a value from a list, push it to another list and return it; or block until one is available
* List
*
* @param source0
* @param destination1
* @param timeout2
* @return BulkReply
*/
@Override
public BulkReply brpoplpush(byte[] source0, byte[] destination1, byte[] timeout2) throws RedisException {
// TODO: Blocking
return null;
}
/**
* Get an element from a list by its index
* List
*
* @param key0
* @param index1
* @return BulkReply
*/
@SuppressWarnings("unchecked")
@Override
public BulkReply lindex(byte[] key0, byte[] index1) throws RedisException {
int index = _toposint(index1);
List list = _getlist(key0, true);
if (list == null || list.size() <= index) {
return NIL_REPLY;
} else {
return new BulkReply(list.get(index).getBytes());
}
}
/**
* Insert an element before or after another element in a list
* List
*
* @param key0
* @param where1
* @param pivot2
* @param value3
* @return IntegerReply
*/
@Override
public IntegerReply linsert(byte[] key0, byte[] where1, byte[] pivot2, byte[] value3) throws RedisException {
Where where = Where.valueOf(new String(where1).toUpperCase());
List list = _getlist(key0, true);
BytesKey pivot = new BytesKey(pivot2);
int i = list.indexOf(pivot);
if (i == -1) {
return integer(-1);
}
list.add(i + (where == Where.BEFORE ? 0 : 1), new BytesKey(value3));
return integer(list.size());
}
enum Where {BEFORE, AFTER}
/**
* Get the length of a list
* List
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply llen(byte[] key0) throws RedisException {
List list = _getlist(key0, false);
return list == null ? integer(0) : integer(list.size());
}
/**
* Remove and get the first element in a list
* List
*
* @param key0
* @return BulkReply
*/
@Override
public BulkReply lpop(byte[] key0) throws RedisException {
List list = _getlist(key0, false);
if (list == null || list.size() == 0) {
return NIL_REPLY;
} else {
return new BulkReply(list.remove(0).getBytes());
}
}
/**
* Prepend one or multiple values to a list
* List
*
* @param key0
* @param value1
* @return IntegerReply
*/
@Override
public IntegerReply lpush(byte[] key0, byte[][] value1) throws RedisException {
List list = _getlist(key0, true);
for (byte[] value : value1) {
list.add(0, new BytesKey(value));
}
return integer(list.size());
}
/**
* Prepend a value to a list, only if the list exists
* List
*
* @param key0
* @param value1
* @return IntegerReply
*/
@Override
public IntegerReply lpushx(byte[] key0, byte[] value1) throws RedisException {
List list = _getlist(key0, false);
if (list == null) {
return integer(0);
} else {
list.add(0, new BytesKey(value1));
}
return integer(list.size());
}
/**
* Get a range of elements from a list
* List
*
* @param key0
* @param start1
* @param stop2
* @return MultiBulkReply
*/
@Override
public MultiBulkReply lrange(byte[] key0, byte[] start1, byte[] stop2) throws RedisException {
List list = _getlist(key0, false);
if (list == null) {
return MultiBulkReply.EMPTY;
} else {
int size = list.size();
int s = _torange(start1, size);
int e = _torange(stop2, size);
if (e < s) e = s;
int length = e - s + 1;
Reply[] replies = new Reply[length];
for (int i = s; i <= e; i++) {
replies[i - s] = new BulkReply(list.get(i).getBytes());
}
return new MultiBulkReply(replies);
}
}
/**
* Remove elements from a list
* List
*
* @param key0
* @param count1
* @param value2
* @return IntegerReply
*/
@Override
public IntegerReply lrem(byte[] key0, byte[] count1, byte[] value2) throws RedisException {
List list = _getlist(key0, false);
if (list == null) {
return integer(0);
} else {
int count = _toint(count1);
BytesKey value = new BytesKey(value2);
int size = list.size();
int dir = 1;
int s = 0;
int e = size;
int rem = 0;
boolean all = count == 0;
if (count < 0) {
count = -count;
dir = -1;
s = e;
e = -1;
}
for (int i = s; (all || count != 0) && i != e; i += dir) {
if (list.get(i).equals(value)) {
list.remove(i);
e -= dir;
i -= dir;
rem++;
count--;
}
}
return integer(rem);
}
}
/**
* Set the value of an element in a list by its index
* List
*
* @param key0
* @param index1
* @param value2
* @return StatusReply
*/
@Override
public StatusReply lset(byte[] key0, byte[] index1, byte[] value2) throws RedisException {
List list = _getlist(key0, false);
if (list == null) {
throw noSuchKey();
}
int size = list.size();
int index = _toposint(index1);
if (index < size) {
list.set(index, new BytesKey(value2));
return OK;
} else {
throw invalidValue();
}
}
/**
* Trim a list to the specified range
* List
*
* @param key0
* @param start1
* @param stop2
* @return StatusReply
*/
@Override
public StatusReply ltrim(byte[] key0, byte[] start1, byte[] stop2) throws RedisException {
List list = _getlist(key0, false);
if (list == null) {
return OK;
} else {
int l = list.size();
int s = _torange(start1, l);
int e = _torange(stop2, l);
// Doesn't change expiration
data.put(key0, list.subList(s, e + 1));
return OK;
}
}
/**
* Remove and get the last element in a list
* List
*
* @param key0
* @return BulkReply
*/
@Override
public BulkReply rpop(byte[] key0) throws RedisException {
List list = _getlist(key0, false);
int l;
if (list == null || (l = list.size()) == 0) {
return NIL_REPLY;
} else {
byte[] bytes = list.get(l - 1).getBytes();
list.remove(l - 1);
return new BulkReply(bytes);
}
}
/**
* Remove the last element in a list, append it to another list and return it
* List
*
* @param source0
* @param destination1
* @return BulkReply
*/
@Override
public BulkReply rpoplpush(byte[] source0, byte[] destination1) throws RedisException {
List source = _getlist(source0, false);
int l;
if (source == null || (l = source.size()) == 0) {
return NIL_REPLY;
} else {
List dest = _getlist(destination1, true);
BytesValue popped = source.get(l - 1);
source.remove(l - 1);
dest.add(0, popped);
return new BulkReply(popped.getBytes());
}
}
/**
* Append one or multiple values to a list
* List
*
* @param key0
* @param value1
* @return IntegerReply
*/
@Override
public IntegerReply rpush(byte[] key0, byte[][] value1) throws RedisException {
List list = _getlist(key0, true);
for (byte[] bytes : value1) {
list.add(new BytesKey(bytes));
}
return integer(list.size());
}
/**
* Append a value to a list, only if the list exists
* List
*
* @param key0
* @param value1
* @return IntegerReply
*/
@Override
public IntegerReply rpushx(byte[] key0, byte[] value1) throws RedisException {
List list = _getlist(key0, false);
if (list == null) {
return integer(0);
} else {
list.add(new BytesKey(value1));
return integer(list.size());
}
}
/**
* Delete a key
* Generic
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply del(byte[][] key0) throws RedisException {
int total = 0;
for (byte[] bytes : key0) {
Object remove = data.remove(bytes);
if (remove != null) {
total++;
}
expires.remove(bytes);
}
return integer(total);
}
/**
* Return a serialized version of the value stored at the specified key.
* Generic
*
* @param key0
* @return BulkReply
*/
@Override
public BulkReply dump(byte[] key0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Determine if a key exists
* Generic
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply exists(byte[] key0) throws RedisException {
Object o = _get(key0);
return o == null ? integer(0) : integer(1);
}
/**
* Set a key's time to live in seconds
* Generic
*
* @param key0
* @param seconds1
* @return IntegerReply
*/
@Override
public IntegerReply expire(byte[] key0, byte[] seconds1) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(0);
} else {
expires.put(key0, bytesToNum(seconds1) * 1000 + now());
return integer(1);
}
}
/**
* Set the expiration for a key as a UNIX timestamp
* Generic
*
* @param key0
* @param timestamp1
* @return IntegerReply
*/
@Override
public IntegerReply expireat(byte[] key0, byte[] timestamp1) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(0);
} else {
expires.put(key0, bytesToNum(timestamp1) * 1000);
return integer(1);
}
}
/**
* Find all keys matching the given pattern
* Generic
*
* @param pattern0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply keys(byte[] pattern0) throws RedisException {
if (pattern0 == null) {
throw new RedisException("wrong number of arguments for KEYS");
}
List> replies = new ArrayList>();
Iterator it = data.keySet().iterator();
while(it.hasNext()) {
BytesKey key = (BytesKey) it.next();
byte[] bytes = key.getBytes();
boolean expired = false;
Long l = expires.get(key);
if (l != null) {
if (l < now()) {
expired = true;
it.remove();
}
}
if (matches(bytes, pattern0, 0, 0) && !expired) {
replies.add(new BulkReply(bytes));
}
}
return new MultiBulkReply(replies.toArray(new Reply[replies.size()]));
}
/**
* Atomically transfer a key from a Redis instance to another one.
* Generic
*
* @param host0
* @param port1
* @param key2
* @param destination_db3
* @param timeout4
* @return StatusReply
*/
@Override
public StatusReply migrate(byte[] host0, byte[] port1, byte[] key2, byte[] destination_db3, byte[] timeout4) throws RedisException {
// TODO: Multiserver
return null;
}
/**
* Move a key to another database
* Generic
*
* @param key0
* @param db1
* @return IntegerReply
*/
@Override
public IntegerReply move(byte[] key0, byte[] db1) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Inspect the internals of Redis objects
* Generic
*
* @param subcommand0
* @param arguments1
* @return Reply
*/
@Override
public Reply object(byte[] subcommand0, byte[][] arguments1) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Remove the expiration from a key
* Generic
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply persist(byte[] key0) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(0);
} else {
Long remove = expires.remove(key0);
return remove == null ? integer(0) : integer(1);
}
}
/**
* Set a key's time to live in milliseconds
* Generic
*
* @param key0
* @param milliseconds1
* @return IntegerReply
*/
@Override
public IntegerReply pexpire(byte[] key0, byte[] milliseconds1) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(0);
} else {
expires.put(key0, bytesToNum(milliseconds1) + now());
return integer(1);
}
}
/**
* Set the expiration for a key as a UNIX timestamp specified in milliseconds
* Generic
*
* @param key0
* @param milliseconds_timestamp1
* @return IntegerReply
*/
@Override
public IntegerReply pexpireat(byte[] key0, byte[] milliseconds_timestamp1) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(0);
} else {
expires.put(key0, bytesToNum(milliseconds_timestamp1));
return integer(1);
}
}
/**
* Get the time to live for a key in milliseconds
* Generic
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply pttl(byte[] key0) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(-1);
} else {
Long aLong = expires.get(key0);
if (aLong == null) {
return integer(-1);
} else {
return integer(aLong - now());
}
}
}
/**
* Return a random key from the keyspace
* Generic
*
* @return BulkReply
*/
@Override
public BulkReply randomkey() throws RedisException {
// This implementation mirrors that of Redis. I'm not
// sure I believe that this is a great algorithm but
// it beats the alternatives that are very inefficient.
if (tableField != null) {
int size = data.size();
if (size == 0) {
return NIL_REPLY;
}
try {
BytesKey key = getRandomKey(data);
return new BulkReply(key.getBytes());
} catch (Exception e) {
throw new RedisException(e);
}
}
return null;
}
private BytesKey getRandomKey(Map data1) throws IllegalAccessException {
Map.Entry[] table = (Map.Entry[]) tableField.get(data1);
int length = table.length;
Map.Entry entry;
do {
entry = table[r.nextInt(length)];
} while (entry == null);
int entries = 0;
Map.Entry current = entry;
do {
entries++;
current = (Map.Entry) nextField.get(current);
} while (current != null);
int choose = r.nextInt(entries);
current = entry;
while (choose-- != 0) current = (Map.Entry) nextField.get(current);
return (BytesKey) current.getKey();
}
/**
* Rename a key
* Generic
*
* @param key0
* @param newkey1
* @return StatusReply
*/
@Override
public StatusReply rename(byte[] key0, byte[] newkey1) throws RedisException {
Object o = _get(key0);
if (o == null) {
throw noSuchKey();
} else {
data.put(newkey1, data.remove(key0));
expires.put(newkey1, expires.remove(key0));
return OK;
}
}
/**
* Rename a key, only if the new key does not exist
* Generic
*
* @param key0
* @param newkey1
* @return IntegerReply
*/
@Override
public IntegerReply renamenx(byte[] key0, byte[] newkey1) throws RedisException {
Object o = _get(key0);
if (o == null) {
throw noSuchKey();
} else {
Object newo = _get(newkey1);
if (newo == null) {
data.put(newkey1, data.remove(key0));
expires.put(newkey1, expires.remove(key0));
return integer(1);
} else {
return integer(0);
}
}
}
/**
* Create a key using the provided serialized value, previously obtained using DUMP.
* Generic
*
* @param key0
* @param ttl1
* @param serialized_value2
* @return StatusReply
*/
@Override
public StatusReply restore(byte[] key0, byte[] ttl1, byte[] serialized_value2) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Sort the elements in a list, set or sorted set
* Generic
*
* SORT key [BY pattern]
* [LIMIT offset count]
* [GET pattern [GET pattern ...]]
* [ASC|DESC]
* [ALPHA]
* [STORE destination]
*
* @param key0
* @param pattern1_offset_or_count2_pattern3
*
* @return Reply
*/
@Override
public Reply sort(byte[] key0, byte[][] pattern1_offset_or_count2_pattern3) throws RedisException {
// TODO
return null;
}
/**
* Get the time to live for a key
* Generic
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply ttl(byte[] key0) throws RedisException {
Object o = _get(key0);
if (o == null) {
return integer(-1);
} else {
Long aLong = expires.get(key0);
if (aLong == null) {
return integer(-1);
} else {
return integer((aLong - now()) / 1000);
}
}
}
/**
* Determine the type stored at key
* Generic
*
* @param key0
* @return StatusReply
*/
@Override
public StatusReply type(byte[] key0) throws RedisException {
Object o = _get(key0);
if (o == null) {
return new StatusReply("none");
} else if (o instanceof byte[]) {
return new StatusReply("string");
} else if (o instanceof Map) {
return new StatusReply("hash");
} else if (o instanceof List) {
return new StatusReply("list");
} else if (o instanceof SortedSet) {
return new StatusReply("zset");
} else if (o instanceof Set) {
return new StatusReply("set");
}
return null;
}
/**
* Forget about all watched keys
* Transactions
*
* @return StatusReply
*/
@Override
public StatusReply unwatch() throws RedisException {
// TODO: Transactions
return null;
}
/**
* Watch the given keys to determine execution of the MULTI/EXEC block
* Transactions
*
* @param key0
* @return StatusReply
*/
@Override
public StatusReply watch(byte[][] key0) throws RedisException {
// TODO: Transactions
return null;
}
/**
* Execute a Lua script server side
* Scripting
*
* @param script0
* @param numkeys1
* @param key2
* @return Reply
*/
@Override
public Reply eval(byte[] script0, byte[] numkeys1, byte[][] key2) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Execute a Lua script server side
* Scripting
*
* @param sha10
* @param numkeys1
* @param key2
* @return Reply
*/
@Override
public Reply evalsha(byte[] sha10, byte[] numkeys1, byte[][] key2) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Check existence of scripts in the script cache.
* Scripting
*
* @param script0
* @return Reply
*/
@Override
public Reply script_exists(byte[][] script0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Remove all the scripts from the script cache.
* Scripting
*
* @return Reply
*/
@Override
public Reply script_flush() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Kill the script currently in execution.
* Scripting
*
* @return Reply
*/
@Override
public Reply script_kill() throws RedisException {
throw new RedisException("Not supported");
}
/**
* Load the specified Lua script into the script cache.
* Scripting
*
* @param script0
* @return Reply
*/
@Override
public Reply script_load(byte[] script0) throws RedisException {
throw new RedisException("Not supported");
}
/**
* Delete one or more hash fields
* Hash
*
* @param key0
* @param field1
* @return IntegerReply
*/
@Override
public IntegerReply hdel(byte[] key0, byte[][] field1) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, false);
int total = 0;
for (byte[] hkey : field1) {
total += hash.remove(hkey) == null ? 0 : 1;
}
return integer(total);
}
/**
* Determine if a hash field exists
* Hash
*
* @param key0
* @param field1
* @return IntegerReply
*/
@Override
public IntegerReply hexists(byte[] key0, byte[] field1) throws RedisException {
return _gethash(key0, false).get(field1) == null ? integer(0) : integer(1);
}
/**
* Get the value of a hash field
* Hash
*
* @param key0
* @param field1
* @return BulkReply
*/
@Override
public BulkReply hget(byte[] key0, byte[] field1) throws RedisException {
byte[] bytes = _gethash(key0, false).get(field1);
if (bytes == null) {
return NIL_REPLY;
} else {
return new BulkReply(bytes);
}
}
/**
* Get all the fields and values in a hash
* Hash
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply hgetall(byte[] key0) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, false);
int size = hash.size();
Reply[] replies = new Reply[size * 2];
int i = 0;
for (Map.Entry entry : hash.entrySet()) {
replies[i++] = new BulkReply(((BytesKey) entry.getKey()).getBytes());
replies[i++] = new BulkReply(entry.getValue());
}
return new MultiBulkReply(replies);
}
/**
* Increment the integer value of a hash field by the given number
* Hash
*
* @param key0
* @param field1
* @param increment2
* @return IntegerReply
*/
@Override
public IntegerReply hincrby(byte[] key0, byte[] field1, byte[] increment2) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, true);
byte[] field = hash.get(field1);
int increment = _toint(increment2);
if (field == null) {
hash.put(field1, increment2);
return new IntegerReply(increment);
} else {
int i = _toint(field);
int value = i + increment;
hash.put(field1, numToBytes(value, false));
return new IntegerReply(value);
}
}
/**
* Increment the float value of a hash field by the given amount
* Hash
*
* @param key0
* @param field1
* @param increment2
* @return BulkReply
*/
@Override
public BulkReply hincrbyfloat(byte[] key0, byte[] field1, byte[] increment2) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, true);
byte[] field = hash.get(field1);
double increment = _todouble(increment2);
if (field == null) {
hash.put(field1, increment2);
return new BulkReply(increment2);
} else {
double d = _todouble(field);
double value = d + increment;
byte[] bytes = _tobytes(value);
hash.put(field1, bytes);
return new BulkReply(bytes);
}
}
/**
* Get all the fields in a hash
* Hash
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply hkeys(byte[] key0) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, false);
int size = hash.size();
Reply[] replies = new Reply[size];
int i = 0;
for (Object hkey : hash.keySet()) {
replies[i++] = new BulkReply(((BytesKey) hkey).getBytes());
}
return new MultiBulkReply(replies);
}
/**
* Get the number of fields in a hash
* Hash
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply hlen(byte[] key0) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, false);
return integer(hash.size());
}
/**
* Get the values of all the given hash fields
* Hash
*
* @param key0
* @param field1
* @return MultiBulkReply
*/
@Override
public MultiBulkReply hmget(byte[] key0, byte[][] field1) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, false);
int length = field1.length;
Reply[] replies = new Reply[length];
for (int i = 0; i < length; i++) {
byte[] bytes = hash.get(field1[i]);
if (bytes == null) {
replies[i] = NIL_REPLY;
} else {
replies[i] = new BulkReply(bytes);
}
}
return new MultiBulkReply(replies);
}
/**
* Set multiple hash fields to multiple values
* Hash
*
* @param key0
* @param field_or_value1
* @return StatusReply
*/
@Override
public StatusReply hmset(byte[] key0, byte[][] field_or_value1) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, true);
if (field_or_value1.length % 2 != 0) {
throw new RedisException("wrong number of arguments for HMSET");
}
for (int i = 0; i < field_or_value1.length; i += 2) {
hash.put(field_or_value1[i], field_or_value1[i + 1]);
}
return OK;
}
/**
* Set the string value of a hash field
* Hash
*
* @param key0
* @param field1
* @param value2
* @return IntegerReply
*/
@Override
public IntegerReply hset(byte[] key0, byte[] field1, byte[] value2) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, true);
Object put = hash.put(field1, value2);
return put == null ? integer(1) : integer(0);
}
/**
* Set the value of a hash field, only if the field does not exist
* Hash
*
* @param key0
* @param field1
* @param value2
* @return IntegerReply
*/
@Override
public IntegerReply hsetnx(byte[] key0, byte[] field1, byte[] value2) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, true);
byte[] bytes = hash.get(field1);
if (bytes == null) {
hash.put(field1, value2);
return integer(1);
} else {
return integer(0);
}
}
/**
* Get all the values in a hash
* Hash
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply hvals(byte[] key0) throws RedisException {
BytesKeyObjectMap hash = _gethash(key0, false);
int size = hash.size();
Reply[] replies = new Reply[size];
int i = 0;
for (byte[] hvalue : hash.values()) {
replies[i++] = new BulkReply(hvalue);
}
return new MultiBulkReply(replies);
}
/**
* Post a message to a channel
* Pubsub
*
* @param channel0
* @param message1
* @return IntegerReply
*/
@Override
public IntegerReply publish(byte[] channel0, byte[] message1) throws RedisException {
// TODO: Pubsub
return null;
}
/**
* Add one or more members to a set
* Set
*
* @param key0
* @param member1
* @return IntegerReply
*/
@Override
public IntegerReply sadd(byte[] key0, byte[][] member1) throws RedisException {
BytesKeySet set = _getset(key0, true);
int total = 0;
for (byte[] bytes : member1) {
if (set.add(bytes)) total++;
}
return integer(total);
}
/**
* Get the number of members in a set
* Set
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply scard(byte[] key0) throws RedisException {
BytesKeySet bytesKeys = _getset(key0, false);
return integer(bytesKeys.size());
}
/**
* Subtract multiple sets
* Set
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply sdiff(byte[][] key0) throws RedisException {
BytesKeySet set = _sdiff(key0);
return _setreply(set);
}
private BytesKeySet _sdiff(byte[][] key0) throws RedisException {
BytesKeySet set = null;
for (byte[] key : key0) {
if (set == null) {
set = new BytesKeySet();
set.addAll(_getset(key, false));
} else {
BytesKeySet c = _getset(key, false);
set.removeAll(c);
}
}
if (set == null) {
throw new RedisException("wrong number of arguments for 'sdiff' command");
}
return set;
}
/**
* Subtract multiple sets and store the resulting set in a key
* Set
*
* @param destination0
* @param key1
* @return IntegerReply
*/
@Override
public IntegerReply sdiffstore(byte[] destination0, byte[][] key1) throws RedisException {
Object o = _get(destination0);
if (o == null || o instanceof Set) {
BytesKeySet set = _sdiff(key1);
_put(destination0, set);
return integer(set.size());
} else {
throw invalidValue();
}
}
/**
* Intersect multiple sets
* Set
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply sinter(byte[][] key0) throws RedisException {
BytesKeySet set = _sinter(key0);
return _setreply(set);
}
private BytesKeySet _sinter(byte[][] key0) throws RedisException {
BytesKeySet set = null;
for (byte[] key : key0) {
if (set == null) {
set = _getset(key, false);
} else {
BytesKeySet inter = new BytesKeySet();
BytesKeySet newset = _getset(key, false);
for (BytesKey bytesKey : newset) {
if (set.contains(bytesKey)) {
inter.add(bytesKey);
}
}
set = inter;
}
}
if (set == null) {
throw new RedisException("wrong number of arguments for 'sinter' command");
}
return set;
}
/**
* Intersect multiple sets and store the resulting set in a key
* Set
*
* @param destination0
* @param key1
* @return IntegerReply
*/
@Override
public IntegerReply sinterstore(byte[] destination0, byte[][] key1) throws RedisException {
Object o = _get(destination0);
if (o == null || o instanceof Set) {
BytesKeySet set = _sinter(key1);
_put(destination0, set);
return integer(set.size());
} else {
throw invalidValue();
}
}
/**
* Determine if a given value is a member of a set
* Set
*
* @param key0
* @param member1
* @return IntegerReply
*/
@Override
public IntegerReply sismember(byte[] key0, byte[] member1) throws RedisException {
BytesKeySet set = _getset(key0, false);
return set.contains(member1) ? integer(1) : integer(0);
}
/**
* Get all the members in a set
* Set
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply smembers(byte[] key0) throws RedisException {
BytesKeySet set = _getset(key0, false);
return _setreply(set);
}
private MultiBulkReply _setreply(BytesKeySet set) {
Reply[] replies = new Reply[set.size()];
int i = 0;
for (BytesKey value : set) {
replies[i++] = new BulkReply(value.getBytes());
}
return new MultiBulkReply(replies);
}
/**
* Move a member from one set to another
* Set
*
* @param source0
* @param destination1
* @param member2
* @return IntegerReply
*/
@Override
public IntegerReply smove(byte[] source0, byte[] destination1, byte[] member2) throws RedisException {
BytesKeySet source = _getset(source0, false);
if (source.remove(member2)) {
BytesKeySet dest = _getset(destination1, true);
dest.add(member2);
return integer(1);
} else {
return integer(0);
}
}
/**
* Remove and return a random member from a set
* Set
*
* @param key0
* @return BulkReply
*/
@Override
public BulkReply spop(byte[] key0) throws RedisException {
if (mapField == null || tableField == null) {
throw new RedisException("Not supported");
}
BytesKeySet set = _getset(key0, false);
if (set.size() == 0) return NIL_REPLY;
try {
BytesKey key = getRandomKey((Map) mapField.get(set));
set.remove(key);
return new BulkReply(key.getBytes());
} catch (IllegalAccessException e) {
throw new RedisException("Not supported");
}
}
/**
* Get a random member from a set
* Set
*
* @param key0
* @return BulkReply
*/
@Override
public Reply srandmember(byte[] key0, byte[] count1) throws RedisException {
if (mapField == null || tableField == null) {
throw new RedisException("Not supported");
}
BytesKeySet set = _getset(key0, false);
int size = set.size();
try {
if (count1 == null) {
if (size == 0) return NIL_REPLY;
BytesKey key = getRandomKey((Map) mapField.get(set));
return new BulkReply(key.getBytes());
} else {
int count = _toint(count1);
int distinct = count < 0 ? -1 : 1;
count *= distinct;
if (count > size && distinct > 0) count = size;
Reply[] replies = new Reply[count];
Set found;
if (distinct > 0) {
found = new HashSet(count);
} else {
found = null;
}
for (int i = 0; i < count; i++) {
BytesKey key;
do {
key = getRandomKey((Map) mapField.get(set));
} while (found != null && !found.add(key));
replies[i] = new BulkReply(key.getBytes());
}
return new MultiBulkReply(replies);
}
} catch (IllegalAccessException e) {
throw new RedisException("Not supported");
}
}
/**
* Remove one or more members from a set
* Set
*
* @param key0
* @param member1
* @return IntegerReply
*/
@Override
public IntegerReply srem(byte[] key0, byte[][] member1) throws RedisException {
BytesKeySet set = _getset(key0, false);
int total = 0;
for (byte[] member : member1) {
if (set.remove(member)) {
total++;
}
}
return new IntegerReply(total);
}
/**
* Add multiple sets
* Set
*
* @param key0
* @return MultiBulkReply
*/
@Override
public MultiBulkReply sunion(byte[][] key0) throws RedisException {
BytesKeySet set = _sunion(key0);
return _setreply(set);
}
private BytesKeySet _sunion(byte[][] key0) throws RedisException {
BytesKeySet set = null;
for (byte[] key : key0) {
if (set == null) {
set = new BytesKeySet();
set.addAll(_getset(key, false));
} else {
set.addAll(_getset(key, false));
}
}
if (set == null) {
throw new RedisException("wrong number of arguments for 'sunion' command");
}
return set;
}
/**
* Add multiple sets and store the resulting set in a key
* Set
*
* @param destination0
* @param key1
* @return IntegerReply
*/
@Override
public IntegerReply sunionstore(byte[] destination0, byte[][] key1) throws RedisException {
Object o = _get(destination0);
if (o == null || o instanceof Set) {
BytesKeySet set = _sunion(key1);
_put(destination0, set);
return integer(set.size());
} else {
throw invalidValue();
}
}
/**
* Add one or more members to a sorted set, or update its score if it already exists
* Sorted_set
*
* @param args
* @return IntegerReply
*/
@Override
public IntegerReply zadd(byte[][] args) throws RedisException {
if (args.length < 3 || (args.length - 1) % 2 == 1) {
throw new RedisException("wrong number of arguments for 'zadd' command");
}
byte[] key = args[0];
ZSet zset = _getzset(key, true);
int total = 0;
for (int i = 1; i < args.length; i += 2) {
byte[] value = args[i + 1];
byte[] score = args[i];
if (zset.add(new BytesKey(value), _todouble(score))) {
total++;
}
}
return integer(total);
}
private double _todouble(byte[] score) {
return parseDouble(new String(score));
}
/**
* Get the number of members in a sorted set
* Sorted_set
*
* @param key0
* @return IntegerReply
*/
@Override
public IntegerReply zcard(byte[] key0) throws RedisException {
ZSet zset = _getzset(key0, false);
return integer(zset.size());
}
/**
* Count the members in a sorted set with scores within the given values
* Sorted_set
*
* @param key0
* @param min1
* @param max2
* @return IntegerReply
*/
@Override
public IntegerReply zcount(byte[] key0, byte[] min1, byte[] max2) throws RedisException {
if (key0 == null || min1 == null || max2 == null) {
throw new RedisException("wrong number of arguments for 'zcount' command");
}
ZSet zset = _getzset(key0, false);
Score min = _toscorerange(min1);
Score max = _toscorerange(max2);
Iterable entries = zset.subSet(_todouble(min1), _todouble(max2));
int total = 0;
for (ZSetEntry entry : entries) {
if (entry.getScore() == min.value && !min.inclusive) {
continue;
}
if (entry.getScore() == max.value && !max.inclusive) {
continue;
}
total++;
}
return integer(total);
}
/**
* Increment the score of a member in a sorted set
* Sorted_set
*
* @param key0
* @param increment1
* @param member2
* @return BulkReply
*/
@Override
public BulkReply zincrby(byte[] key0, byte[] increment1, byte[] member2) throws RedisException {
ZSet zset = _getzset(key0, true);
ZSetEntry entry = zset.get(member2);
double increment = _todouble(increment1);
if (entry == null) {
zset.add(new BytesKey(member2), increment);
return new BulkReply(increment1);
} else {
zset.remove(member2);
zset.add(entry.getKey(), entry.getScore() + increment);
return new BulkReply(_tobytes(entry.getScore()));
}
}
/**
* Intersect multiple sorted sets and store the resulting sorted set in a new key
* Sorted_set
*
* @param destination0
* @param numkeys1
* @param key2
* @return IntegerReply
*/
@Override
public IntegerReply zinterstore(byte[] destination0, byte[] numkeys1, byte[][] key2) throws RedisException {
return _zstore(destination0, numkeys1, key2, "zinterstore", false);
}
private IntegerReply _zstore(byte[] destination0, byte[] numkeys1, byte[][] key2, String name, boolean union) throws RedisException {
if (destination0 == null || numkeys1 == null) {
throw new RedisException("wrong number of arguments for '" + name + "' command");
}
int numkeys = _toint(numkeys1);
if (key2.length < numkeys) {
throw new RedisException("wrong number of arguments for '" + name + "' command");
}
int position = numkeys;
double[] weights = null;
Aggregate type = null;
if (key2.length > position) {
if ("weights".equals(new String(key2[position]).toLowerCase())) {
position++;
if (key2.length < position + numkeys) {
throw new RedisException("wrong number of arguments for '" + name + "' command");
}
weights = new double[numkeys];
for (int i = position; i < position + numkeys; i++) {
weights[i - position] = _todouble(key2[i]);
}
position += numkeys;
}
if (key2.length > position + 1) {
if ("aggregate".equals(new String(key2[position]).toLowerCase())) {
type = Aggregate.valueOf(new String(key2[position + 1]).toUpperCase());
}
} else if (key2.length != position) {
throw new RedisException("wrong number of arguments for '" + name + "' command");
}
}
del(new byte[][]{destination0});
ZSet destination = _getzset(destination0, true);
for (int i = 0; i < numkeys; i++) {
ZSet zset = _getzset(key2[i], false);
if (i == 0) {
if (weights == null) {
destination.addAll(zset);
} else {
double weight = weights[i];
for (ZSetEntry entry : zset) {
destination.add(entry.getKey(), entry.getScore() * weight);
}
}
} else {
for (ZSetEntry entry : zset) {
BytesKey key = entry.getKey();
ZSetEntry current = destination.get(key);
destination.remove(key);
if (union || current != null) {
double newscore = entry.getScore() * (weights == null ? 1 : weights[i]);
if (type == null || type == Aggregate.SUM) {
if (current != null) {
newscore += current.getScore();
}
} else if (type == Aggregate.MIN) {
if (current != null && newscore > current.getScore()) {
newscore = current.getScore();
}
} else if (type == Aggregate.MAX) {
if (current != null && newscore < current.getScore()) {
newscore = current.getScore();
}
}
destination.add(key, newscore);
}
}
if (!union) {
for (ZSetEntry entry : new ZSet(destination)) {
BytesKey key = entry.getKey();
if (zset.get(key) == null) {
destination.remove(key);
}
}
}
}
}
return integer(destination.size());
}
enum Aggregate {SUM, MIN, MAX}
/**
* Return a range of members in a sorted set, by index
* Sorted_set
*
* @param key0
* @param start1
* @param stop2
* @param withscores3
* @return MultiBulkReply
*/
@Override
public MultiBulkReply zrange(byte[] key0, byte[] start1, byte[] stop2, byte[] withscores3) throws RedisException {
if (key0 == null || start1 == null || stop2 == null) {
throw new RedisException("invalid number of argumenst for 'zrange' command");
}
boolean withscores = _checkcommand(withscores3, "withscores", true);
ZSet zset = _getzset(key0, false);
int size = zset.size();
int start = _torange(start1, size);
int end = _torange(stop2, size);
Iterator iterator = zset.iterator();
List> list = new ArrayList>();
for (int i = 0; i < size; i++) {
if (iterator.hasNext()) {
ZSetEntry next = iterator.next();
if (i >= start && i <= end) {
list.add(new BulkReply(next.getKey().getBytes()));
if (withscores) {
list.add(new BulkReply(_tobytes(next.getScore())));
}
} else if (i > end) {
break;
}
}
}
return new MultiBulkReply(list.toArray(new Reply[list.size()]));
}
private boolean _checkcommand(byte[] check, String command, boolean syntax) throws RedisException {
boolean result;
if (check != null) {
if (new String(check).toLowerCase().equals(command)) {
result = true;
} else {
if (syntax) {
throw new RedisException("syntax error");
} else {
return false;
}
}
} else {
result = false;
}
return result;
}
/**
* Return a range of members in a sorted set, by score
* Sorted_set
*
* @param key0
* @param min1
* @param max2
* @param withscores_offset_or_count4
* @return MultiBulkReply
*/
@Override
public MultiBulkReply zrangebyscore(byte[] key0, byte[] min1, byte[] max2, byte[][] withscores_offset_or_count4) throws RedisException {
ZSet zset = _getzset(key0, false);
if (zset.isEmpty()) return MultiBulkReply.EMPTY;
List> list = _zrangebyscore(min1, max2, withscores_offset_or_count4, zset, false);
return new MultiBulkReply(list.toArray(new Reply[list.size()]));
}
private List> _zrangebyscore(byte[] min1, byte[] max2, byte[][] withscores_offset_or_count4, ZSet zset, boolean reverse) throws RedisException {
int position = 0;
boolean withscores = false;
if (withscores_offset_or_count4.length > 0) {
withscores = _checkcommand(withscores_offset_or_count4[0], "withscores", false);
}
if (withscores) position++;
boolean limit = false;
if (withscores_offset_or_count4.length > position) {
limit = _checkcommand(withscores_offset_or_count4[position++], "limit", true);
}
if (withscores_offset_or_count4.length != position + (limit ? 2 : 0)) {
throw new RedisException("syntax error");
}
int offset = 0;
int number = Integer.MAX_VALUE;
if (limit) {
offset = _toint(withscores_offset_or_count4[position++]);
number = _toint(withscores_offset_or_count4[position]);
if (offset < 0 || number < 1) {
throw notInteger();
}
}
Score min = _toscorerange(min1);
Score max = _toscorerange(max2);
List entries = zset.subSet(min.value, max.value);
if (reverse) Collections.reverse(entries);
int current = 0;
List> list = new ArrayList>();
for (ZSetEntry entry : entries) {
if (current >= offset && current < offset + number) {
list.add(new BulkReply(entry.getKey().getBytes()));
if (withscores) list.add(new BulkReply(_tobytes(entry.getScore())));
}
current++;
}
return list;
}
private Score _toscorerange(byte[] specifier) {
Score score = new Score();
String s = new String(specifier).toLowerCase();
if (s.startsWith("(")) {
score.inclusive = false;
s = s.substring(1);
}
if (s.equals("-inf")) {
score.value = Double.NEGATIVE_INFINITY;
} else if (s.equals("inf") || s.equals("+inf")) {
score.value = Double.POSITIVE_INFINITY;
} else {
score.value = Double.parseDouble(s);
}
return score;
}
static class Score {
boolean inclusive = true;
double value;
}
/**
* Determine the index of a member in a sorted set
* Sorted_set
*
* @param key0
* @param member1
* @return Reply
*/
@Override
public Reply zrank(byte[] key0, byte[] member1) throws RedisException {
List zset = _getzset(key0, false).list();
return _zrank(member1, zset);
}
/**
* Remove one or more members from a sorted set
* Sorted_set
*
* @param key0
* @param member1
* @return IntegerReply
*/
@Override
public IntegerReply zrem(byte[] key0, byte[][] member1) throws RedisException {
ZSet zset = _getzset(key0, false);
if (zset.isEmpty()) return integer(0);
int total = 0;
for (byte[] member : member1) {
if (zset.remove(member)) {
total++;
}
}
return integer(total);
}
/**
* Remove all members in a sorted set within the given indexes
* Sorted_set
*
* @param key0
* @param start1
* @param stop2
* @return IntegerReply
*/
@Override
public IntegerReply zremrangebyrank(byte[] key0, byte[] start1, byte[] stop2) throws RedisException {
ZSet zset = _getzset(key0, false);
if (zset.isEmpty()) return integer(0);
int size = zset.size();
int start = _torange(start1, size);
int end = _torange(stop2, size);
Iterator iterator = zset.iterator();
List list = new ArrayList();
for (int i = 0; i < size; i++) {
if (iterator.hasNext()) {
ZSetEntry next = iterator.next();
if (i >= start && i <= end) {
list.add(next);
} else if (i > end) {
break;
}
}
}
int total = 0;
for (ZSetEntry zSetEntry : list) {
if (zset.remove(zSetEntry.getKey())) total++;
}
return integer(total);
}
/**
* Remove all members in a sorted set within the given scores
* Sorted_set
*
* @param key0
* @param min1
* @param max2
* @return IntegerReply
*/
@Override
public IntegerReply zremrangebyscore(byte[] key0, byte[] min1, byte[] max2) throws RedisException {
ZSet zset = _getzset(key0, false);
if (zset.isEmpty()) return integer(0);
Score min = _toscorerange(min1);
Score max = _toscorerange(max2);
List entries = zset.subSet(min.value, max.value);
int total = 0;
for (ZSetEntry entry : new ArrayList(entries)) {
if (!min.inclusive && entry.getScore() == min.value) continue;
if (!max.inclusive && entry.getScore() == max.value) continue;
if (zset.remove(entry.getKey())) {
total++;
}
}
return integer(total);
}
/**
* Return a range of members in a sorted set, by index, with scores ordered from high to low
* Sorted_set
*
* @param key0
* @param start1
* @param stop2
* @param withscores3
* @return MultiBulkReply
*/
@Override
public MultiBulkReply zrevrange(byte[] key0, byte[] start1, byte[] stop2, byte[] withscores3) throws RedisException {
if (key0 == null || start1 == null || stop2 == null) {
throw new RedisException("invalid number of argumenst for 'zrevrange' command");
}
boolean withscores = _checkcommand(withscores3, "withscores", true);
ZSet zset = _getzset(key0, false);
int size = zset.size();
int end = size - _torange(start1, size) - 1;
int start = size - _torange(stop2, size) - 1;
Iterator iterator = zset.iterator();
List> list = new ArrayList>();
for (int i = 0; i < size; i++) {
if (iterator.hasNext()) {
ZSetEntry next = iterator.next();
if (i >= start && i <= end) {
list.add(0, new BulkReply(next.getKey().getBytes()));
if (withscores) {
list.add(1, new BulkReply(_tobytes(next.getScore())));
}
} else if (i > end) {
break;
}
}
}
return new MultiBulkReply(list.toArray(new Reply[list.size()]));
}
/**
* Return a range of members in a sorted set, by score, with scores ordered from high to low
* Sorted_set
*
* @param key0
* @param max1
* @param min2
* @param withscores_offset_or_count4
* @return MultiBulkReply
*/
@Override
public MultiBulkReply zrevrangebyscore(byte[] key0, byte[] max1, byte[] min2, byte[][] withscores_offset_or_count4) throws RedisException {
ZSet zset = _getzset(key0, false);
if (zset.isEmpty()) return MultiBulkReply.EMPTY;
List> list = _zrangebyscore(min2, max1, withscores_offset_or_count4, zset, true);
return new MultiBulkReply(list.toArray(new Reply[list.size()]));
}
/**
* Determine the index of a member in a sorted set, with scores ordered from high to low
* Sorted_set
*
* @param key0
* @param member1
* @return Reply
*/
@Override
public Reply zrevrank(byte[] key0, byte[] member1) throws RedisException {
List zset = _getzset(key0, false).list();
Collections.reverse(zset);
return _zrank(member1, zset);
}
private Reply _zrank(byte[] member1, List zset) {
BytesKey member = new BytesKey(member1);
int position = 0;
for (ZSetEntry entry : zset) {
if (entry.getKey().equals(member)) {
return integer(position);
}
position++;
}
return NIL_REPLY;
}
/**
* Get the score associated with the given member in a sorted set
* Sorted_set
*
* @param key0
* @param member1
* @return BulkReply
*/
@Override
public BulkReply zscore(byte[] key0, byte[] member1) throws RedisException {
ZSet zset = _getzset(key0, false);
ZSetEntry entry = zset.get(member1);
double score = entry.getScore();
return new BulkReply(_tobytes(score));
}
private byte[] _tobytes(double score) {
return String.valueOf(score).getBytes();
}
/**
* Add multiple sorted sets and store the resulting sorted set in a new key
* Sorted_set
*
* @param destination0
* @param numkeys1
* @param key2
* @return IntegerReply
*/
@Override
public IntegerReply zunionstore(byte[] destination0, byte[] numkeys1, byte[][] key2) throws RedisException {
return _zstore(destination0, numkeys1, key2, "zunionstore", true);
}
}