com.google.common.collect.TreeRangeSet Maven / Gradle / Ivy
/*
* Copyright (C) 2011 The Guava Authors
*
* 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 com.google.common.collect;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.GwtIncompatible;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;
/**
* An implementation of {@link RangeSet} backed by a {@link TreeMap}.
*
* @author Louis Wasserman
*/
@GwtIncompatible("uses NavigableMap") final class TreeRangeSet
extends RangeSet {
// TODO(user): override inefficient defaults
private final NavigableMap, Range> rangesByLowerCut;
/**
* Creates an empty {@code TreeRangeSet} instance.
*/
public static TreeRangeSet create() {
return new TreeRangeSet(new TreeMap, Range>());
}
private TreeRangeSet(NavigableMap, Range> rangesByLowerCut) {
this.rangesByLowerCut = rangesByLowerCut;
}
private transient Set> asRanges;
@Override
public Set> asRanges() {
Set> result = asRanges;
return (result == null) ? asRanges = new AsRanges() : result;
}
final class AsRanges extends ForwardingCollection> implements Set> {
@Override
protected Collection> delegate() {
return rangesByLowerCut.values();
}
@Override
public int hashCode() {
return Sets.hashCodeImpl(this);
}
@Override
public boolean equals(@Nullable Object o) {
return Sets.equalsImpl(this, o);
}
}
@Override
@Nullable
public Range rangeContaining(C value) {
checkNotNull(value);
Entry, Range> floorEntry = rangesByLowerCut.floorEntry(Cut.belowValue(value));
if (floorEntry != null && floorEntry.getValue().contains(value)) {
return floorEntry.getValue();
} else {
// TODO(kevinb): revisit this design choice
return null;
}
}
@Override
public boolean encloses(Range range) {
checkNotNull(range);
Entry, Range> floorEntry = rangesByLowerCut.floorEntry(range.lowerBound);
return floorEntry != null && floorEntry.getValue().encloses(range);
}
@Override
public void add(Range rangeToAdd) {
checkNotNull(rangeToAdd);
if (rangeToAdd.isEmpty()) {
return;
}
// We will use { } to illustrate ranges currently in the range set, and < >
// to illustrate rangeToAdd.
Cut lbToAdd = rangeToAdd.lowerBound;
Cut ubToAdd = rangeToAdd.upperBound;
Entry, Range> entryBelowLB = rangesByLowerCut.lowerEntry(lbToAdd);
if (entryBelowLB != null) {
// { <
Range rangeBelowLB = entryBelowLB.getValue();
if (rangeBelowLB.upperBound.compareTo(lbToAdd) >= 0) {
// { < }, and we will need to coalesce
if (rangeBelowLB.upperBound.compareTo(ubToAdd) >= 0) {
// { < > }
ubToAdd = rangeBelowLB.upperBound;
/*
* TODO(cpovirk): can we just "return;" here? Or, can we remove this if() entirely? If
* not, add tests to demonstrate the problem with each approach
*/
}
lbToAdd = rangeBelowLB.lowerBound;
}
}
Entry, Range> entryBelowUB = rangesByLowerCut.floorEntry(ubToAdd);
if (entryBelowUB != null) {
// { >
Range rangeBelowUB = entryBelowUB.getValue();
if (rangeBelowUB.upperBound.compareTo(ubToAdd) >= 0) {
// { > }, and we need to coalesce
ubToAdd = rangeBelowUB.upperBound;
}
}
// Remove ranges which are strictly enclosed.
rangesByLowerCut.subMap(lbToAdd, ubToAdd).clear();
replaceRangeWithSameLowerBound(new Range(lbToAdd, ubToAdd));
}
@Override
public void remove(Range rangeToRemove) {
checkNotNull(rangeToRemove);
if (rangeToRemove.isEmpty()) {
return;
}
// We will use { } to illustrate ranges currently in the range set, and < >
// to illustrate rangeToRemove.
Entry, Range> entryBelowLB = rangesByLowerCut.lowerEntry(rangeToRemove.lowerBound);
if (entryBelowLB != null) {
// { <
Range rangeBelowLB = entryBelowLB.getValue();
if (rangeBelowLB.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) {
// { < }, and we will need to subdivide
if (rangeBelowLB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) {
// { < > }
replaceRangeWithSameLowerBound(
new Range(rangeToRemove.upperBound, rangeBelowLB.upperBound));
}
replaceRangeWithSameLowerBound(
new Range(rangeBelowLB.lowerBound, rangeToRemove.lowerBound));
}
}
Entry, Range> entryBelowUB = rangesByLowerCut.floorEntry(rangeToRemove.upperBound);
if (entryBelowUB != null) {
// { >
Range rangeBelowUB = entryBelowUB.getValue();
if (rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) {
// { > }
replaceRangeWithSameLowerBound(
new Range(rangeToRemove.upperBound, rangeBelowUB.upperBound));
}
}
rangesByLowerCut.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear();
}
private void replaceRangeWithSameLowerBound(Range range) {
if (range.isEmpty()) {
rangesByLowerCut.remove(range.lowerBound);
} else {
rangesByLowerCut.put(range.lowerBound, range);
}
}
private transient RangeSet complement;
@Override
public RangeSet complement() {
RangeSet result = complement;
return (result == null) ? complement = createComplement() : result;
}
private RangeSet createComplement() {
return new StandardComplement(this);
}
}