org.apache.cassandra.utils.IntervalTree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-all Show documentation
Show all versions of cassandra-all Show documentation
The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cassandra.utils;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import com.google.common.base.Joiner;
import org.apache.cassandra.utils.AbstractIterator;
import com.google.common.collect.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.AsymmetricOrdering.Op;
public class IntervalTree, D, I extends Interval> implements Iterable
{
private static final Logger logger = LoggerFactory.getLogger(IntervalTree.class);
@SuppressWarnings("unchecked")
private static final IntervalTree EMPTY_TREE = new IntervalTree(null);
private final IntervalNode head;
private final int count;
protected IntervalTree(Collection intervals)
{
this.head = intervals == null || intervals.isEmpty() ? null : new IntervalNode(intervals);
this.count = intervals == null ? 0 : intervals.size();
}
public static , D, I extends Interval> IntervalTree build(Collection intervals)
{
if (intervals == null || intervals.isEmpty())
return emptyTree();
return new IntervalTree(intervals);
}
public static , D, I extends Interval> Serializer serializer(ISerializer pointSerializer, ISerializer dataSerializer, Constructor constructor)
{
return new Serializer<>(pointSerializer, dataSerializer, constructor);
}
@SuppressWarnings("unchecked")
public static , D, I extends Interval> IntervalTree emptyTree()
{
return EMPTY_TREE;
}
public int intervalCount()
{
return count;
}
public boolean isEmpty()
{
return head == null;
}
public C max()
{
if (head == null)
throw new IllegalStateException();
return head.high;
}
public C min()
{
if (head == null)
throw new IllegalStateException();
return head.low;
}
public List search(Interval searchInterval)
{
if (head == null)
return Collections.emptyList();
List results = new ArrayList();
head.searchInternal(searchInterval, results);
return results;
}
public List search(C point)
{
return search(Interval.create(point, point, null));
}
public Iterator iterator()
{
if (head == null)
return Collections.emptyIterator();
return new TreeIterator(head);
}
@Override
public String toString()
{
return "<" + Joiner.on(", ").join(this) + ">";
}
@Override
public boolean equals(Object o)
{
if(!(o instanceof IntervalTree))
return false;
IntervalTree that = (IntervalTree)o;
return Iterators.elementsEqual(iterator(), that.iterator());
}
@Override
public final int hashCode()
{
int result = 0;
for (Interval interval : this)
result = 31 * result + interval.hashCode();
return result;
}
private class IntervalNode
{
final C center;
final C low;
final C high;
final List intersectsLeft;
final List intersectsRight;
final IntervalNode left;
final IntervalNode right;
public IntervalNode(Collection toBisect)
{
assert !toBisect.isEmpty();
logger.trace("Creating IntervalNode from {}", toBisect);
// Building IntervalTree with one interval will be a reasonably
// common case for range tombstones, so it's worth optimizing
if (toBisect.size() == 1)
{
I interval = toBisect.iterator().next();
low = interval.min;
center = interval.max;
high = interval.max;
List l = Collections.singletonList(interval);
intersectsLeft = l;
intersectsRight = l;
left = null;
right = null;
}
else
{
// Find min, median and max
List allEndpoints = new ArrayList(toBisect.size() * 2);
for (I interval : toBisect)
{
allEndpoints.add(interval.min);
allEndpoints.add(interval.max);
}
Collections.sort(allEndpoints);
low = allEndpoints.get(0);
center = allEndpoints.get(toBisect.size());
high = allEndpoints.get(allEndpoints.size() - 1);
// Separate interval in intersecting center, left of center and right of center
List intersects = new ArrayList();
List leftSegment = new ArrayList();
List rightSegment = new ArrayList();
for (I candidate : toBisect)
{
if (candidate.max.compareTo(center) < 0)
leftSegment.add(candidate);
else if (candidate.min.compareTo(center) > 0)
rightSegment.add(candidate);
else
intersects.add(candidate);
}
intersectsLeft = Interval.minOrdering().sortedCopy(intersects);
intersectsRight = Interval.maxOrdering().sortedCopy(intersects);
left = leftSegment.isEmpty() ? null : new IntervalNode(leftSegment);
right = rightSegment.isEmpty() ? null : new IntervalNode(rightSegment);
assert (intersects.size() + leftSegment.size() + rightSegment.size()) == toBisect.size() :
"intersects (" + String.valueOf(intersects.size()) +
") + leftSegment (" + String.valueOf(leftSegment.size()) +
") + rightSegment (" + String.valueOf(rightSegment.size()) +
") != toBisect (" + String.valueOf(toBisect.size()) + ")";
}
}
void searchInternal(Interval searchInterval, List results)
{
if (center.compareTo(searchInterval.min) < 0)
{
int i = Interval.maxOrdering().binarySearchAsymmetric(intersectsRight, searchInterval.min, Op.CEIL);
if (i == intersectsRight.size() && high.compareTo(searchInterval.min) < 0)
return;
while (i < intersectsRight.size())
results.add(intersectsRight.get(i++).data);
if (right != null)
right.searchInternal(searchInterval, results);
}
else if (center.compareTo(searchInterval.max) > 0)
{
int j = Interval.minOrdering().binarySearchAsymmetric(intersectsLeft, searchInterval.max, Op.HIGHER);
if (j == 0 && low.compareTo(searchInterval.max) > 0)
return;
for (int i = 0 ; i < j ; i++)
results.add(intersectsLeft.get(i).data);
if (left != null)
left.searchInternal(searchInterval, results);
}
else
{
// Adds every interval contained in this node to the result set then search left and right for further
// overlapping intervals
for (Interval interval : intersectsLeft)
results.add(interval.data);
if (left != null)
left.searchInternal(searchInterval, results);
if (right != null)
right.searchInternal(searchInterval, results);
}
}
}
private class TreeIterator extends AbstractIterator
{
private final Deque stack = new ArrayDeque();
private Iterator current;
TreeIterator(IntervalNode node)
{
super();
gotoMinOf(node);
}
protected I computeNext()
{
while (true)
{
if (current != null && current.hasNext())
return current.next();
IntervalNode node = stack.pollFirst();
if (node == null)
return endOfData();
current = node.intersectsLeft.iterator();
// We know this is the smaller not returned yet, but before doing
// its parent, we must do everyone on it's right.
gotoMinOf(node.right);
}
}
private void gotoMinOf(IntervalNode node)
{
while (node != null)
{
stack.offerFirst(node);
node = node.left;
}
}
}
public static class Serializer, D, I extends Interval> implements IVersionedSerializer>
{
private final ISerializer pointSerializer;
private final ISerializer dataSerializer;
private final Constructor constructor;
private Serializer(ISerializer pointSerializer, ISerializer dataSerializer, Constructor constructor)
{
this.pointSerializer = pointSerializer;
this.dataSerializer = dataSerializer;
this.constructor = constructor;
}
public void serialize(IntervalTree it, DataOutputPlus out, int version) throws IOException
{
out.writeInt(it.count);
for (Interval interval : it)
{
pointSerializer.serialize(interval.min, out);
pointSerializer.serialize(interval.max, out);
dataSerializer.serialize(interval.data, out);
}
}
/**
* Deserialize an IntervalTree whose keys use the natural ordering.
* Use deserialize(DataInput, int, Comparator) instead if the interval
* tree is to use a custom comparator, as the comparator is *not*
* serialized.
*/
public IntervalTree deserialize(DataInputPlus in, int version) throws IOException
{
return deserialize(in, version, null);
}
public IntervalTree deserialize(DataInputPlus in, int version, Comparator comparator) throws IOException
{
try
{
int count = in.readInt();
List intervals = new ArrayList(count);
for (int i = 0; i < count; i++)
{
C min = pointSerializer.deserialize(in);
C max = pointSerializer.deserialize(in);
D data = dataSerializer.deserialize(in);
intervals.add(constructor.newInstance(min, max, data));
}
return new IntervalTree(intervals);
}
catch (InstantiationException | InvocationTargetException | IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
public long serializedSize(IntervalTree it, int version)
{
long size = TypeSizes.sizeof(0);
for (Interval interval : it)
{
size += pointSerializer.serializedSize(interval.min);
size += pointSerializer.serializedSize(interval.max);
size += dataSerializer.serializedSize(interval.data);
}
return size;
}
}
}