/* * 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); } }