package net.tomp2p.dht;
import java.util.ArrayList;
import java.util.Collection;
import java.util.NavigableMap;
import java.util.TreeMap;
final public class RangeLock<K extends Comparable<K>> {
private final Object lockInternal = new Object();
private final NavigableMap<K, Long> cache = new TreeMap<K, Long>();
final public class Range {
final private K fromKey;
final private K toKey;
final private RangeLock<K> ref;
private Range(final K fromKey, final K toKey, RangeLock<K> ref) {
this.fromKey = fromKey;
this.toKey = toKey;
this.ref = ref;
}
public void unlock() {
ref.unlock(this);
}
}
public Range tryLock(final K fromKey, final K toKey) {
final long id = Thread.currentThread().getId();
synchronized (lockInternal) {
//first check overlappings or smaller subset of keys
final NavigableMap<K, Long> subMap = cache.subMap(fromKey, true, toKey, true);
if (!subMap.isEmpty()) {
if(sizeFiltered(id, subMap) != 0) {
return null;
}
}
//second check larger subset of keys
final Collection<Long> before = cache.headMap(fromKey, false).values();
final Collection<Long> after = cache.tailMap(toKey, false).values();
//now check for intersection thread ids
final Collection<Long> intersection = intersection(before, after);
if(!intersection.isEmpty()) {
if(sizeFiltered(id, intersection) != 0) {
return null;
}
}
cache.put(fromKey, id);
cache.put(toKey, id);
}
return new Range(fromKey, toKey, this);
}
/**
* The same thread can lock a range twice. The first unlock for range x unlocks all range x.
* @param fromKey
* @param toKey
* @return
*/
public Range lock(final K fromKey, final K toKey) {
final long id = Thread.currentThread().getId();
synchronized (lockInternal) {
final NavigableMap<K, Long> subMap = cache.subMap(fromKey, true, toKey, true);
final Collection<Long> before = cache.headMap(fromKey, false).values();
final Collection<Long> after = cache.tailMap(toKey, false).values();
Collection<Long> intersection = null;
while (!(intersection = intersection(before, after)).isEmpty() || !subMap.isEmpty()) {
if((subMap.isEmpty() || sizeFiltered(id, subMap) == 0) &&
(intersection.isEmpty() || sizeFiltered(id, intersection) == 0)) {
break;
}
try {
lockInternal.wait();
} catch (InterruptedException e) {
return null;
}
}
cache.put(fromKey, id);
cache.put(toKey, id);
}
return new Range(fromKey, toKey, this);
}
//make a copy!
private Collection<Long> intersection(final Collection<Long> before,
final Collection<Long> after) {
final Collection<Long> intersection = new ArrayList<Long>(before);
intersection.retainAll(after);
return intersection;
}
public void unlock(RangeLock<?>.Range lock) {
synchronized (lockInternal) {
cache.remove(lock.fromKey);
cache.remove(lock.toKey);
lockInternal.notifyAll();
}
}
public int size() {
synchronized (lockInternal) {
return cache.size();
}
}
private static int sizeFiltered(final long id, final NavigableMap<?, Long> subMap) {
int counter = 0;
for(final long longValue:subMap.values()) {
if(longValue != id) {
counter ++;
}
}
return counter;
}
private static int sizeFiltered(final long id, final Collection<Long> intersection) {
int counter = 0;
for(final long longValue:intersection) {
if(longValue != id) {
counter ++;
}
}
return counter;
}
}