Java tutorial
/** * Copyright 2014 Nikita Koksharov, Nickolay Borbit * * 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 io.netty.channel.EventLoop; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Promise; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; import java.security.MessageDigest; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.SortedSet; import org.redisson.async.ResultOperation; import org.redisson.async.SyncOperation; import org.redisson.connection.ConnectionManager; import org.redisson.core.RSortedSet; import com.lambdaworks.redis.RedisAsyncConnection; import com.lambdaworks.redis.RedisConnection; import com.lambdaworks.redis.RedisMovedException; /** * * @author Nikita Koksharov * * @param <V> */ public class RedissonSortedSet<V> extends RedissonObject implements RSortedSet<V> { private static class NaturalComparator<V> implements Comparator<V>, Serializable { private static final long serialVersionUID = 7207038068494060240L; static final NaturalComparator NATURAL_ORDER = new NaturalComparator(); public int compare(V c1, V c2) { Comparable<Object> c1co = (Comparable<Object>) c1; Comparable<Object> c2co = (Comparable<Object>) c2; return c1co.compareTo(c2co); } } public static class BinarySearchResult<V> { private V value; private int index; public BinarySearchResult(V value) { super(); this.value = value; } public BinarySearchResult() { } public void setIndex(int index) { this.index = index; } public int getIndex() { return index; } public V getValue() { return value; } } private Comparator<? super V> comparator = NaturalComparator.NATURAL_ORDER; protected RedissonSortedSet(ConnectionManager connectionManager, String name) { super(connectionManager, name); loadComparator(); connectionManager.write(getName(), new ResultOperation<Boolean, Object>() { @Override protected Future<Boolean> execute(RedisAsyncConnection<Object, Object> async) { return async.setnx(getCurrentVersionKey(), 0L); } }); } private void loadComparator() { connectionManager.read(getName(), new SyncOperation<V, Void>() { @Override public Void execute(RedisConnection<Object, V> conn) { loadComparator(conn); return null; } }); } private void loadComparator(RedisConnection<Object, ?> connection) { try { String comparatorSign = (String) connection.get(getComparatorKeyName()); 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<V>) clazz.newInstance(); } } catch (RedisMovedException 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 int size() { return connectionManager.read(getName(), new ResultOperation<Long, V>() { @Override protected Future<Long> execute(RedisAsyncConnection<Object, V> async) { return async.llen(getName()); } }).intValue(); } private int size(RedisConnection<Object, ?> connection) { return connection.llen(getName()).intValue(); } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean contains(final Object o) { return connectionManager.read(getName(), new SyncOperation<V, Boolean>() { @Override public Boolean execute(RedisConnection<Object, V> conn) { return binarySearch((V) o, conn).getIndex() >= 0; } }); } public Iterator<V> iterator() { final int ind = 0; return new Iterator<V>() { private int currentIndex = ind - 1; private boolean removeExecuted; @Override public boolean hasNext() { int size = size(); return currentIndex + 1 < size && size > 0; } @Override public V next() { if (!hasNext()) { throw new NoSuchElementException("No such element at index " + currentIndex); } currentIndex++; removeExecuted = false; return RedissonSortedSet.this.get(currentIndex); } @Override public void remove() { if (removeExecuted) { throw new IllegalStateException("Element been already deleted"); } RedissonSortedSet.this.remove(currentIndex); currentIndex--; removeExecuted = true; } }; // Double startScore; // RedisConnection<Object, V> connection = connectionManager.connectionReadOp(); // try { // startScore = getScoreAtIndex(0, connection); // } finally { // connectionManager.releaseRead(connection); // } // if (startScore == null) { // return new Iterator<V>() { // @Override // public boolean hasNext() { // return false; // } // // @Override // public V next() { // throw new NoSuchElementException(); // } // // @Override // public void remove() { // } // }; // } // // return iterator(startScore, Double.MAX_VALUE); } private void remove(final int index) { connectionManager.write(getName(), new SyncOperation<Object, V>() { @Override public V execute(RedisConnection<Object, Object> conn) { if (index == 0) { return (V) conn.lpop(getName()); } while (true) { conn.watch(getName()); List<Object> tail = conn.lrange(getName(), index + 1, size()); conn.multi(); conn.ltrim(getName(), 0, index - 1); if (tail.isEmpty()) { if (conn.exec().size() == 1) { return null; } } else { conn.rpush(getName(), tail.toArray()); if (conn.exec().size() == 2) { return null; } } } } }); } private V get(final int index) { return connectionManager.read(getName(), new ResultOperation<V, V>() { @Override protected Future<V> execute(RedisAsyncConnection<Object, V> async) { return async.lindex(getName(), index); } }); } @Override public Object[] toArray() { List<V> res = connectionManager.read(getName(), new ResultOperation<List<V>, V>() { @Override protected Future<List<V>> execute(RedisAsyncConnection<Object, V> async) { return async.lrange(getName(), 0, -1); } }); return res.toArray(); } @Override public <T> T[] toArray(T[] a) { List<V> res = connectionManager.read(getName(), new ResultOperation<List<V>, V>() { @Override protected Future<List<V>> execute(RedisAsyncConnection<Object, V> async) { return async.lrange(getName(), 0, -1); } }); return res.toArray(a); } private String getCurrentVersionKey() { return "redisson__sortedset__version__{" + getName() + "}"; } private Long getCurrentVersion(RedisConnection<Object, Object> simpleConnection) { return ((Number) simpleConnection.get(getCurrentVersionKey())).longValue(); } @Override public boolean add(final V value) { return connectionManager.write(getName(), new SyncOperation<V, Boolean>() { @Override public Boolean execute(RedisConnection<Object, V> conn) { return add(value, conn); } }); } public Future<Boolean> addAsync(final V value) { EventLoop loop = connectionManager.getGroup().next(); final Promise<Boolean> promise = loop.newPromise(); loop.execute(new Runnable() { @Override public void run() { try { boolean result = add(value); promise.setSuccess(result); } catch (Exception e) { promise.setFailure(e); } } }); return promise; } boolean add(V value, RedisConnection<Object, V> connection) { RedisConnection<Object, Object> simpleConnection = (RedisConnection<Object, Object>) connection; while (true) { connection.watch(getName(), getComparatorKeyName()); checkComparator(connection); Long version = getCurrentVersion(simpleConnection); BinarySearchResult<V> res = binarySearch(value, connection); if (res.getIndex() < 0) { // System.out.println("index: " + res.getIndex() + " value: " + value); if (!version.equals(getCurrentVersion(simpleConnection))) { connection.unwatch(); continue; } // NewScore newScore = calcNewScore(res.getIndex(), connection); // if (!version.equals(getCurrentVersion(simpleConnection))) { // connection.unwatch(); // continue; // } // // String leftScoreKey = getScoreKeyName(newScore.getLeftScore()); // String rightScoreKey = getScoreKeyName(newScore.getRightScore()); // // if (simpleConnection.setnx(leftScoreKey, 1)) { // if (!version.equals(getCurrentVersion(simpleConnection))) { // connection.unwatch(); // // connection.del(leftScoreKey); // continue; // } // if (rightScoreKey != null) { // // if (!simpleConnection.setnx(rightScoreKey, 1)) { // connection.unwatch(); // // connection.del(leftScoreKey); // continue; // } // } // } else { // connection.unwatch(); // continue; // } V pivot = null; boolean before = false; int index = -(res.getIndex() + 1); if (index < size()) { before = true; pivot = connection.lindex(getName(), index); } connection.multi(); if (index >= size()) { connection.rpush(getName(), value); } else { connection.linsert(getName(), before, pivot, value); } // System.out.println("adding: " + newScore.getScore() + " " + value); // connection.zadd(getName(), newScore.getScore(), value); // if (rightScoreKey != null) { // connection.del(leftScoreKey, rightScoreKey); // } else { // connection.del(leftScoreKey); // } connection.incr(getCurrentVersionKey()); List<Object> re = connection.exec(); if (re.size() == 2) { // System.out.println("index: " + index + " value: " + value + " pivot: " + pivot); return true; // Number val = (Number) re.get(0); // Long delCount = (Long) re.get(1); // if (rightScoreKey != null) { // if (delCount != 2) { // throw new IllegalStateException(); // } // } else { // if (delCount != 1) { // throw new IllegalStateException(); // } // } // return val != null && val.intValue() > 0; } } else { connection.unwatch(); return false; } } } private void checkComparator(RedisConnection<Object, ?> connection) { String comparatorSign = (String) connection.get(getComparatorKeyName()); if (comparatorSign != null) { String[] vals = comparatorSign.split(":"); String className = vals[0]; if (!comparator.getClass().getName().equals(className)) { try { loadComparator(connection); } finally { connection.unwatch(); } } } } public static double calcIncrement(double value) { BigDecimal b = BigDecimal.valueOf(value); BigDecimal r = b.remainder(BigDecimal.ONE); if (r.compareTo(BigDecimal.ZERO) == 0) { return 1; } double res = 1 / Math.pow(10, r.scale()); return res; } @Override public Future<Boolean> removeAsync(final V value) { EventLoop loop = connectionManager.getGroup().next(); final Promise<Boolean> promise = loop.newPromise(); loop.execute(new Runnable() { @Override public void run() { try { boolean result = remove(value); promise.setSuccess(result); } catch (Exception e) { promise.setFailure(e); } } }); return promise; } @Override public boolean remove(final Object value) { return connectionManager.write(getName(), new SyncOperation<V, Boolean>() { @Override public Boolean execute(RedisConnection<Object, V> conn) { return remove(value, conn); } }); } boolean remove(Object value, RedisConnection<Object, V> conn) { while (true) { conn.watch(getName()); BinarySearchResult<V> res = binarySearch((V) value, conn); if (res.getIndex() < 0) { conn.unwatch(); return false; } if (res.getIndex() == 0) { conn.multi(); conn.lpop(getName()); if (conn.exec().size() == 1) { return true; } } RedisConnection<Object, Object> ?onnection = (RedisConnection<Object, Object>)conn; List<Object> tail = ?onnection.lrange(getName(), res.getIndex() + 1, size()); ?onnection.multi(); ?onnection.ltrim(getName(), 0, res.getIndex() - 1); if (tail.isEmpty()) { if (?onnection.exec().size() == 1) { return true; } } else { ?onnection.rpush(getName(), tail.toArray()); if (?onnection.exec().size() == 2) { return true; } } } } @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; } @Override public SortedSet<V> subSet(V fromElement, V toElement) { throw new UnsupportedOperationException(); // return new RedissonSubSortedSet<V>(this, connectionManager, fromElement, toElement); } @Override public SortedSet<V> headSet(V toElement) { return subSet(null, toElement); } @Override public SortedSet<V> tailSet(V fromElement) { return subSet(fromElement, null); } @Override public V first() { V res = connectionManager.read(getName(), new ResultOperation<V, V>() { @Override protected Future<V> execute(RedisAsyncConnection<Object, V> async) { return async.lindex(getName(), 0); } }); if (res == null) { throw new NoSuchElementException(); } return res; } @Override public V last() { V res = connectionManager.read(getName(), new ResultOperation<V, V>() { @Override protected Future<V> execute(RedisAsyncConnection<Object, V> async) { return async.lindex(getName(), -1); } }); if (res == null) { throw new NoSuchElementException(); } return res; } private String getScoreKeyName(int index) { return "redisson__sortedset__score__" + getName() + "__" + index; } private String getComparatorKeyName() { return "redisson__sortedset__comparator__{" + getName() + "}"; } @Override public boolean trySetComparator(final Comparator<? super V> comparator) { return connectionManager.write(getName(), new SyncOperation<String, Boolean>() { @Override public Boolean execute(RedisConnection<Object, String> connection) { connection.watch(getName(), getComparatorKeyName()); if (size(connection) > 0) { connection.unwatch(); return false; } connection.multi(); String className = comparator.getClass().getName(); String comparatorSign = className + ":" + calcClassSign(className); connection.set(getComparatorKeyName(), comparatorSign); List<Object> res = connection.exec(); if (res.size() == 1) { RedissonSortedSet.this.comparator = comparator; return true; } return false; } }); } private V getAtIndex(int index, RedisConnection<Object, V> connection) { return connection.lindex(getName(), index); } /** * Binary search algorithm * * @param value * @param connection * @param lowerIndex * @param upperIndex * @return */ private BinarySearchResult<V> binarySearch(V value, RedisConnection<Object, V> connection, int lowerIndex, int upperIndex) { while (lowerIndex <= upperIndex) { int index = lowerIndex + (upperIndex - lowerIndex) / 2; V res = getAtIndex(index, connection); int cmp = comparator.compare(value, res); if (cmp == 0) { BinarySearchResult<V> indexRes = new BinarySearchResult<V>(); indexRes.setIndex(index); return indexRes; } else if (cmp < 0) { upperIndex = index - 1; } else { lowerIndex = index + 1; } } BinarySearchResult<V> indexRes = new BinarySearchResult<V>(); indexRes.setIndex(-(lowerIndex + 1)); return indexRes; } public BinarySearchResult<V> binarySearch(V value, RedisConnection<Object, V> connection) { int upperIndex = size(connection) - 1; return binarySearch(value, connection, 0, upperIndex); } double score(V value, RedisConnection<Object, V> connection, int indexDiff, boolean tail) { return -1; // BinarySearchResult<V> res = binarySearch(value, connection); // if (res.getIndex() < 0) { // V element = getAtIndex(-res.getIndex() + indexDiff, connection); // if (element.getScore() == null && res.getScore() == null && tail) { // element = getAtIndex(-res.getIndex() - 2, connection); // return element.getScore(); // } // return element.getScore(); // } // int ind = res.getIndex(); // if (tail) { // ind = res.getIndex() - indexDiff; // } // BinarySearchResult<V> element = getAtIndex(ind, connection); // return element.getScore(); } public String toString() { Iterator<V> 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(' '); } } }