io.scalecube.cluster.gossip.SequenceIdCollector Maven / Gradle / Ivy
package io.scalecube.cluster.gossip;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* A collector that efficiently store sequence of incremented numbers which go in a row without
* missed elements.
*/
public class SequenceIdCollector {
// store closed intervals [a, b] without intersections
// if there is an intersection between tow intervals then they will be merged into one
private final TreeMap processedInterval = new TreeMap<>();
// [a, b]
private static boolean isInClosedRange(Entry range, long element) {
return range != null && range.getKey() <= element && element <= range.getValue();
}
private static boolean isNextToClosedRange(Entry range, long element) {
return range != null && (element + 1 == range.getKey() || element - 1 == range.getValue());
}
/**
* Returns true if this set contains the specified element.
*
* @param sequenceId element whose presence in this set is to be tested
* @return true if this set contains the specified element
*/
public boolean contains(long sequenceId) {
// floor: returns the entry for the greatest key less than or equal to the specified key
return isInClosedRange(processedInterval.floorEntry(sequenceId), sequenceId);
}
/**
* Adds the specified element to this holder if it is not already present.
*
* @param sequenceId element to be added
* @return true if this holder did not already contain the specified element
*/
public boolean add(long sequenceId) {
// floor: returns the entry for the greatest key less than or equal to the specified key
final Entry floorEntry = processedInterval.floorEntry(sequenceId);
if (isInClosedRange(floorEntry, sequenceId)) {
return false;
}
// ceiling: returns the entry for the least key greater than or equal to the specified key
final Entry ceilingEntry = processedInterval.ceilingEntry(sequenceId);
final boolean nextToFloor = isNextToClosedRange(floorEntry, sequenceId);
final boolean nextToCeiling = isNextToClosedRange(ceilingEntry, sequenceId);
if (nextToFloor && nextToCeiling) {
processedInterval.remove(floorEntry.getKey());
processedInterval.remove(ceilingEntry.getKey());
processedInterval.put(floorEntry.getKey(), ceilingEntry.getValue());
} else if (nextToFloor) {
processedInterval.remove(floorEntry.getKey());
processedInterval.put(floorEntry.getKey(), sequenceId);
} else if (nextToCeiling) {
processedInterval.remove(ceilingEntry.getKey());
processedInterval.put(sequenceId, ceilingEntry.getValue());
} else {
processedInterval.put(sequenceId, sequenceId);
}
return true;
}
/**
* Returns the number of intervals in this collector.
*
* @return the number of intervals in this collector
*/
public int size() {
return processedInterval.size();
}
/** Removes all of the elements from this collector. */
public void clear() {
processedInterval.clear();
}
@Override
public String toString() {
return processedInterval.entrySet().stream()
.map(entry -> "[" + entry.getKey() + "," + entry.getValue() + "]")
.collect(Collectors.joining(",", "{", "}"));
}
}