/*
* 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<C extends Comparable>
extends RangeSet<C> {
// TODO(user): override inefficient defaults
private final NavigableMap<Cut<C>, Range<C>> rangesByLowerCut;
/**
* Creates an empty {@code TreeRangeSet} instance.
*/
public static <C extends Comparable> TreeRangeSet<C> create() {
return new TreeRangeSet<C>(new TreeMap<Cut<C>, Range<C>>());
}
private TreeRangeSet(NavigableMap<Cut<C>, Range<C>> rangesByLowerCut) {
this.rangesByLowerCut = rangesByLowerCut;
}
private transient Set<Range<C>> asRanges;
@Override
public Set<Range<C>> asRanges() {
Set<Range<C>> result = asRanges;
return (result == null) ? asRanges = new AsRanges() : result;
}
final class AsRanges extends ForwardingCollection<Range<C>> implements Set<Range<C>> {
@Override
protected Collection<Range<C>> 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<C> rangeContaining(C value) {
checkNotNull(value);
Entry<Cut<C>, Range<C>> 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<C> range) {
checkNotNull(range);
Entry<Cut<C>, Range<C>> floorEntry = rangesByLowerCut.floorEntry(range.lowerBound);
return floorEntry != null && floorEntry.getValue().encloses(range);
}
@Override
public void add(Range<C> rangeToAdd) {
checkNotNull(rangeToAdd);
if (rangeToAdd.isEmpty()) {
return;
}
// We will use { } to illustrate ranges currently in the range set, and < >
// to illustrate rangeToAdd.
Cut<C> lbToAdd = rangeToAdd.lowerBound;
Cut<C> ubToAdd = rangeToAdd.upperBound;
Entry<Cut<C>, Range<C>> entryBelowLB = rangesByLowerCut.lowerEntry(lbToAdd);
if (entryBelowLB != null) {
// { <
Range<C> 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<Cut<C>, Range<C>> entryBelowUB = rangesByLowerCut.floorEntry(ubToAdd);
if (entryBelowUB != null) {
// { >
Range<C> 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<C>(lbToAdd, ubToAdd));
}
@Override
public void remove(Range<C> rangeToRemove) {
checkNotNull(rangeToRemove);
if (rangeToRemove.isEmpty()) {
return;
}
// We will use { } to illustrate ranges currently in the range set, and < >
// to illustrate rangeToRemove.
Entry<Cut<C>, Range<C>> entryBelowLB = rangesByLowerCut.lowerEntry(rangeToRemove.lowerBound);
if (entryBelowLB != null) {
// { <
Range<C> 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<C>(rangeToRemove.upperBound, rangeBelowLB.upperBound));
}
replaceRangeWithSameLowerBound(
new Range<C>(rangeBelowLB.lowerBound, rangeToRemove.lowerBound));
}
}
Entry<Cut<C>, Range<C>> entryBelowUB = rangesByLowerCut.floorEntry(rangeToRemove.upperBound);
if (entryBelowUB != null) {
// { >
Range<C> rangeBelowUB = entryBelowUB.getValue();
if (rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) {
// { > }
replaceRangeWithSameLowerBound(
new Range<C>(rangeToRemove.upperBound, rangeBelowUB.upperBound));
}
}
rangesByLowerCut.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear();
}
private void replaceRangeWithSameLowerBound(Range<C> range) {
if (range.isEmpty()) {
rangesByLowerCut.remove(range.lowerBound);
} else {
rangesByLowerCut.put(range.lowerBound, range);
}
}
private transient RangeSet<C> complement;
@Override
public RangeSet<C> complement() {
RangeSet<C> result = complement;
return (result == null) ? complement = createComplement() : result;
}
private RangeSet<C> createComplement() {
return new StandardComplement<C>(this);
}
}