/* * 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.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeMap; import javax.annotation.Nullable; import com.google.common.annotations.Beta; import com.google.common.annotations.GwtIncompatible; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; /** * An implementation of {@link RangeSet} backed by a {@link TreeMap}. * * @author Louis Wasserman * @since 14.0 */ @Beta @GwtIncompatible("uses NavigableMap") public class TreeRangeSet> extends AbstractRangeSet { @VisibleForTesting final NavigableMap, Range> rangesByLowerBound; /** * Creates an empty {@code TreeRangeSet} instance. */ public static > TreeRangeSet create() { return new TreeRangeSet(new TreeMap, Range>()); } /** * Returns a {@code TreeRangeSet} initialized with the ranges in the specified * range set. */ public static > TreeRangeSet create(RangeSet rangeSet) { TreeRangeSet result = create(); result.addAll(rangeSet); return result; } private TreeRangeSet(NavigableMap, Range> rangesByLowerCut) { this.rangesByLowerBound = 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 rangesByLowerBound.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 = rangesByLowerBound.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 = rangesByLowerBound.floorEntry(range.lowerBound); return floorEntry != null && floorEntry.getValue().encloses(range); } @Nullable private Range rangeEnclosing(Range range) { checkNotNull(range); Entry, Range> floorEntry = rangesByLowerBound.floorEntry(range.lowerBound); return (floorEntry != null && floorEntry.getValue().encloses(range)) ? floorEntry.getValue() : null; } @Override public Range span() { Entry, Range> firstEntry = rangesByLowerBound.firstEntry(); Entry, Range> lastEntry = rangesByLowerBound.lastEntry(); if (firstEntry == null) { throw new NoSuchElementException(); } return Range.create(firstEntry.getValue().lowerBound, lastEntry.getValue().upperBound); } @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 = rangesByLowerBound.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 = rangesByLowerBound.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. rangesByLowerBound.subMap(lbToAdd, ubToAdd).clear(); replaceRangeWithSameLowerBound(Range.create(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 = rangesByLowerBound.lowerEntry(rangeToRemove.lowerBound); if (entryBelowLB != null) { // { < Range rangeBelowLB = entryBelowLB.getValue(); if (rangeBelowLB.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) { // { < }, and we will need to subdivide if (rangeToRemove.hasUpperBound() && rangeBelowLB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { // { < > } replaceRangeWithSameLowerBound(Range.create(rangeToRemove.upperBound, rangeBelowLB.upperBound)); } replaceRangeWithSameLowerBound(Range.create(rangeBelowLB.lowerBound, rangeToRemove.lowerBound)); } } Entry, Range> entryBelowUB = rangesByLowerBound.floorEntry(rangeToRemove.upperBound); if (entryBelowUB != null) { // { > Range rangeBelowUB = entryBelowUB.getValue(); if (rangeToRemove.hasUpperBound() && rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) { // { > } replaceRangeWithSameLowerBound(Range.create(rangeToRemove.upperBound, rangeBelowUB.upperBound)); } } rangesByLowerBound.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear(); } private void replaceRangeWithSameLowerBound(Range range) { if (range.isEmpty()) { rangesByLowerBound.remove(range.lowerBound); } else { rangesByLowerBound.put(range.lowerBound, range); } } private transient RangeSet complement; @Override public RangeSet complement() { RangeSet result = complement; return (result == null) ? complement = new Complement() : result; } @VisibleForTesting static final class RangesByUpperBound> extends AbstractNavigableMap, Range> { private final NavigableMap, Range> rangesByLowerBound; /** * upperBoundWindow represents the headMap/subMap/tailMap view of the entire * "ranges by upper bound" map; it's a constraint on the *keys*, and does not * affect the values. */ private final Range> upperBoundWindow; RangesByUpperBound(NavigableMap, Range> rangesByLowerBound) { this.rangesByLowerBound = rangesByLowerBound; this.upperBoundWindow = Range.all(); } private RangesByUpperBound(NavigableMap, Range> rangesByLowerBound, Range> upperBoundWindow) { this.rangesByLowerBound = rangesByLowerBound; this.upperBoundWindow = upperBoundWindow; } private NavigableMap, Range> subMap(Range> window) { if (window.isConnected(upperBoundWindow)) { return new RangesByUpperBound(rangesByLowerBound, window.intersection(upperBoundWindow)); } else { return ImmutableSortedMap.of(); } } @Override public NavigableMap, Range> subMap(Cut fromKey, boolean fromInclusive, Cut toKey, boolean toInclusive) { return subMap(Range.range(fromKey, BoundType.forBoolean(fromInclusive), toKey, BoundType.forBoolean(toInclusive))); } @Override public NavigableMap, Range> headMap(Cut toKey, boolean inclusive) { return subMap(Range.upTo(toKey, BoundType.forBoolean(inclusive))); } @Override public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) { return subMap(Range.downTo(fromKey, BoundType.forBoolean(inclusive))); } @Override public Comparator> comparator() { return Ordering.>natural(); } @Override public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override public Range get(@Nullable Object key) { if (key instanceof Cut) { try { @SuppressWarnings("unchecked") // we catch CCEs Cut cut = (Cut) key; if (!upperBoundWindow.contains(cut)) { return null; } Entry, Range> candidate = rangesByLowerBound.lowerEntry(cut); if (candidate != null && candidate.getValue().upperBound.equals(cut)) { return candidate.getValue(); } } catch (ClassCastException e) { return null; } } return null; } @Override Iterator, Range>> entryIterator() { /* * We want to start the iteration at the first range where the upper bound is in * upperBoundWindow. */ final Iterator> backingItr; if (!upperBoundWindow.hasLowerBound()) { backingItr = rangesByLowerBound.values().iterator(); } else { Entry, Range> lowerEntry = rangesByLowerBound.lowerEntry(upperBoundWindow.lowerEndpoint()); if (lowerEntry == null) { backingItr = rangesByLowerBound.values().iterator(); } else if (upperBoundWindow.lowerBound.isLessThan(lowerEntry.getValue().upperBound)) { backingItr = rangesByLowerBound.tailMap(lowerEntry.getKey(), true).values().iterator(); } else { backingItr = rangesByLowerBound.tailMap(upperBoundWindow.lowerEndpoint(), true).values().iterator(); } } return new AbstractIterator, Range>>() { @Override protected Entry, Range> computeNext() { if (!backingItr.hasNext()) { return endOfData(); } Range range = backingItr.next(); if (upperBoundWindow.upperBound.isLessThan(range.upperBound)) { return endOfData(); } else { return Maps.immutableEntry(range.upperBound, range); } } }; } @Override Iterator, Range>> descendingEntryIterator() { Collection> candidates; if (upperBoundWindow.hasUpperBound()) { candidates = rangesByLowerBound.headMap(upperBoundWindow.upperEndpoint(), false).descendingMap() .values(); } else { candidates = rangesByLowerBound.descendingMap().values(); } final PeekingIterator> backingItr = Iterators.peekingIterator(candidates.iterator()); if (backingItr.hasNext() && upperBoundWindow.upperBound.isLessThan(backingItr.peek().upperBound)) { backingItr.next(); } return new AbstractIterator, Range>>() { @Override protected Entry, Range> computeNext() { if (!backingItr.hasNext()) { return endOfData(); } Range range = backingItr.next(); return upperBoundWindow.lowerBound.isLessThan(range.upperBound) ? Maps.immutableEntry(range.upperBound, range) : endOfData(); } }; } @Override public int size() { if (upperBoundWindow.equals(Range.all())) { return rangesByLowerBound.size(); } return Iterators.size(entryIterator()); } @Override public boolean isEmpty() { return upperBoundWindow.equals(Range.all()) ? rangesByLowerBound.isEmpty() : !entryIterator().hasNext(); } } private static final class ComplementRangesByLowerBound> extends AbstractNavigableMap, Range> { private final NavigableMap, Range> positiveRangesByLowerBound; private final NavigableMap, Range> positiveRangesByUpperBound; /** * complementLowerBoundWindow represents the headMap/subMap/tailMap view of the * entire "complement ranges by lower bound" map; it's a constraint on the * *keys*, and does not affect the values. */ private final Range> complementLowerBoundWindow; ComplementRangesByLowerBound(NavigableMap, Range> positiveRangesByLowerBound) { this(positiveRangesByLowerBound, Range.>all()); } private ComplementRangesByLowerBound(NavigableMap, Range> positiveRangesByLowerBound, Range> window) { this.positiveRangesByLowerBound = positiveRangesByLowerBound; this.positiveRangesByUpperBound = new RangesByUpperBound(positiveRangesByLowerBound); this.complementLowerBoundWindow = window; } private NavigableMap, Range> subMap(Range> subWindow) { if (!complementLowerBoundWindow.isConnected(subWindow)) { return ImmutableSortedMap.of(); } else { subWindow = subWindow.intersection(complementLowerBoundWindow); return new ComplementRangesByLowerBound(positiveRangesByLowerBound, subWindow); } } @Override public NavigableMap, Range> subMap(Cut fromKey, boolean fromInclusive, Cut toKey, boolean toInclusive) { return subMap(Range.range(fromKey, BoundType.forBoolean(fromInclusive), toKey, BoundType.forBoolean(toInclusive))); } @Override public NavigableMap, Range> headMap(Cut toKey, boolean inclusive) { return subMap(Range.upTo(toKey, BoundType.forBoolean(inclusive))); } @Override public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) { return subMap(Range.downTo(fromKey, BoundType.forBoolean(inclusive))); } @Override public Comparator> comparator() { return Ordering.>natural(); } @Override Iterator, Range>> entryIterator() { /* * firstComplementRangeLowerBound is the first complement range lower bound * inside complementLowerBoundWindow. Complement range lower bounds are either * positive range upper bounds, or Cut.belowAll(). * * positiveItr starts at the first positive range with lower bound greater than * firstComplementRangeLowerBound. (Positive range lower bounds correspond to * complement range upper bounds.) */ Collection> positiveRanges; if (complementLowerBoundWindow.hasLowerBound()) { positiveRanges = positiveRangesByUpperBound.tailMap(complementLowerBoundWindow.lowerEndpoint(), complementLowerBoundWindow.lowerBoundType() == BoundType.CLOSED).values(); } else { positiveRanges = positiveRangesByUpperBound.values(); } final PeekingIterator> positiveItr = Iterators.peekingIterator(positiveRanges.iterator()); final Cut firstComplementRangeLowerBound; if (complementLowerBoundWindow.contains(Cut.belowAll()) && (!positiveItr.hasNext() || positiveItr.peek().lowerBound != Cut.belowAll())) { firstComplementRangeLowerBound = Cut.belowAll(); } else if (positiveItr.hasNext()) { firstComplementRangeLowerBound = positiveItr.next().upperBound; } else { return Iterators.emptyIterator(); } return new AbstractIterator, Range>>() { Cut nextComplementRangeLowerBound = firstComplementRangeLowerBound; @Override protected Entry, Range> computeNext() { if (complementLowerBoundWindow.upperBound.isLessThan(nextComplementRangeLowerBound) || nextComplementRangeLowerBound == Cut.aboveAll()) { return endOfData(); } Range negativeRange; if (positiveItr.hasNext()) { Range positiveRange = positiveItr.next(); negativeRange = Range.create(nextComplementRangeLowerBound, positiveRange.lowerBound); nextComplementRangeLowerBound = positiveRange.upperBound; } else { negativeRange = Range.create(nextComplementRangeLowerBound, Cut.aboveAll()); nextComplementRangeLowerBound = Cut.aboveAll(); } return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); } }; } @Override Iterator, Range>> descendingEntryIterator() { Iterator> itr; /* * firstComplementRangeUpperBound is the upper bound of the last complement * range with lower bound inside complementLowerBoundWindow. * * positiveItr starts at the first positive range with upper bound less than * firstComplementRangeUpperBound. (Positive range upper bounds correspond to * complement range lower bounds.) */ Cut startingPoint = complementLowerBoundWindow.hasUpperBound() ? complementLowerBoundWindow.upperEndpoint() : Cut.aboveAll(); boolean inclusive = complementLowerBoundWindow.hasUpperBound() && complementLowerBoundWindow.upperBoundType() == BoundType.CLOSED; final PeekingIterator> positiveItr = Iterators.peekingIterator( positiveRangesByUpperBound.headMap(startingPoint, inclusive).descendingMap().values().iterator()); Cut cut; if (positiveItr.hasNext()) { cut = (positiveItr.peek().upperBound == Cut.aboveAll()) ? positiveItr.next().lowerBound : positiveRangesByLowerBound.higherKey(positiveItr.peek().upperBound); } else if (!complementLowerBoundWindow.contains(Cut.belowAll()) || positiveRangesByLowerBound.containsKey(Cut.belowAll())) { return Iterators.emptyIterator(); } else { cut = positiveRangesByLowerBound.higherKey(Cut.belowAll()); } final Cut firstComplementRangeUpperBound = Objects.firstNonNull(cut, Cut.aboveAll()); return new AbstractIterator, Range>>() { Cut nextComplementRangeUpperBound = firstComplementRangeUpperBound; @Override protected Entry, Range> computeNext() { if (nextComplementRangeUpperBound == Cut.belowAll()) { return endOfData(); } else if (positiveItr.hasNext()) { Range positiveRange = positiveItr.next(); Range negativeRange = Range.create(positiveRange.upperBound, nextComplementRangeUpperBound); nextComplementRangeUpperBound = positiveRange.lowerBound; if (complementLowerBoundWindow.lowerBound.isLessThan(negativeRange.lowerBound)) { return Maps.immutableEntry(negativeRange.lowerBound, negativeRange); } } else if (complementLowerBoundWindow.lowerBound.isLessThan(Cut.belowAll())) { Range negativeRange = Range.create(Cut.belowAll(), nextComplementRangeUpperBound); nextComplementRangeUpperBound = Cut.belowAll(); return Maps.immutableEntry(Cut.belowAll(), negativeRange); } return endOfData(); } }; } @Override public int size() { return Iterators.size(entryIterator()); } @Override @Nullable public Range get(Object key) { if (key instanceof Cut) { try { @SuppressWarnings("unchecked") Cut cut = (Cut) key; // tailMap respects the current window Entry, Range> firstEntry = tailMap(cut, true).firstEntry(); if (firstEntry != null && firstEntry.getKey().equals(cut)) { return firstEntry.getValue(); } } catch (ClassCastException e) { return null; } } return null; } @Override public boolean containsKey(Object key) { return get(key) != null; } } private final class Complement extends TreeRangeSet { Complement() { super(new ComplementRangesByLowerBound(TreeRangeSet.this.rangesByLowerBound)); } @Override public void add(Range rangeToAdd) { TreeRangeSet.this.remove(rangeToAdd); } @Override public void remove(Range rangeToRemove) { TreeRangeSet.this.add(rangeToRemove); } @Override public boolean contains(C value) { return !TreeRangeSet.this.contains(value); } @Override public RangeSet complement() { return TreeRangeSet.this; } } private static final class SubRangeSetRangesByLowerBound> extends AbstractNavigableMap, Range> { /** * lowerBoundWindow is the headMap/subMap/tailMap view; it only restricts the * keys, and does not affect the values. */ private final Range> lowerBoundWindow; /** * restriction is the subRangeSet view; ranges are truncated to their * intersection with restriction. */ private final Range restriction; private final NavigableMap, Range> rangesByLowerBound; private final NavigableMap, Range> rangesByUpperBound; private SubRangeSetRangesByLowerBound(Range> lowerBoundWindow, Range restriction, NavigableMap, Range> rangesByLowerBound) { this.lowerBoundWindow = checkNotNull(lowerBoundWindow); this.restriction = checkNotNull(restriction); this.rangesByLowerBound = checkNotNull(rangesByLowerBound); this.rangesByUpperBound = new RangesByUpperBound(rangesByLowerBound); } private NavigableMap, Range> subMap(Range> window) { if (!window.isConnected(lowerBoundWindow)) { return ImmutableSortedMap.of(); } else { return new SubRangeSetRangesByLowerBound(lowerBoundWindow.intersection(window), restriction, rangesByLowerBound); } } @Override public NavigableMap, Range> subMap(Cut fromKey, boolean fromInclusive, Cut toKey, boolean toInclusive) { return subMap(Range.range(fromKey, BoundType.forBoolean(fromInclusive), toKey, BoundType.forBoolean(toInclusive))); } @Override public NavigableMap, Range> headMap(Cut toKey, boolean inclusive) { return subMap(Range.upTo(toKey, BoundType.forBoolean(inclusive))); } @Override public NavigableMap, Range> tailMap(Cut fromKey, boolean inclusive) { return subMap(Range.downTo(fromKey, BoundType.forBoolean(inclusive))); } @Override public Comparator> comparator() { return Ordering.>natural(); } @Override public boolean containsKey(@Nullable Object key) { return get(key) != null; } @Override @Nullable public Range get(@Nullable Object key) { if (key instanceof Cut) { try { @SuppressWarnings("unchecked") // we catch CCE's Cut cut = (Cut) key; if (!lowerBoundWindow.contains(cut) || cut.compareTo(restriction.lowerBound) < 0 || cut.compareTo(restriction.upperBound) >= 0) { return null; } else if (cut.equals(restriction.lowerBound)) { // it might be present, truncated on the left Range candidate = Maps.valueOrNull(rangesByLowerBound.floorEntry(cut)); if (candidate != null && candidate.upperBound.compareTo(restriction.lowerBound) > 0) { return candidate.intersection(restriction); } } else { Range result = rangesByLowerBound.get(cut); if (result != null) { return result.intersection(restriction); } } } catch (ClassCastException e) { return null; } } return null; } @Override Iterator, Range>> entryIterator() { if (restriction.isEmpty()) { return Iterators.emptyIterator(); } final Iterator> completeRangeItr; if (lowerBoundWindow.upperBound.isLessThan(restriction.lowerBound)) { return Iterators.emptyIterator(); } else if (lowerBoundWindow.lowerBound.isLessThan(restriction.lowerBound)) { // starts at the first range with upper bound strictly greater than // restriction.lowerBound completeRangeItr = rangesByUpperBound.tailMap(restriction.lowerBound, false).values().iterator(); } else { // starts at the first range with lower bound above lowerBoundWindow.lowerBound completeRangeItr = rangesByLowerBound.tailMap(lowerBoundWindow.lowerBound.endpoint(), lowerBoundWindow.lowerBoundType() == BoundType.CLOSED).values().iterator(); } final Cut> upperBoundOnLowerBounds = Ordering.natural().min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); return new AbstractIterator, Range>>() { @Override protected Entry, Range> computeNext() { if (!completeRangeItr.hasNext()) { return endOfData(); } Range nextRange = completeRangeItr.next(); if (upperBoundOnLowerBounds.isLessThan(nextRange.lowerBound)) { return endOfData(); } else { nextRange = nextRange.intersection(restriction); return Maps.immutableEntry(nextRange.lowerBound, nextRange); } } }; } @Override Iterator, Range>> descendingEntryIterator() { if (restriction.isEmpty()) { return Iterators.emptyIterator(); } Cut> upperBoundOnLowerBounds = Ordering.natural().min(lowerBoundWindow.upperBound, Cut.belowValue(restriction.upperBound)); final Iterator> completeRangeItr = rangesByLowerBound .headMap(upperBoundOnLowerBounds.endpoint(), upperBoundOnLowerBounds.typeAsUpperBound() == BoundType.CLOSED) .descendingMap().values().iterator(); return new AbstractIterator, Range>>() { @Override protected Entry, Range> computeNext() { if (!completeRangeItr.hasNext()) { return endOfData(); } Range nextRange = completeRangeItr.next(); if (restriction.lowerBound.compareTo(nextRange.upperBound) >= 0) { return endOfData(); } nextRange = nextRange.intersection(restriction); if (lowerBoundWindow.contains(nextRange.lowerBound)) { return Maps.immutableEntry(nextRange.lowerBound, nextRange); } else { return endOfData(); } } }; } @Override public int size() { return Iterators.size(entryIterator()); } } @Override public RangeSet subRangeSet(Range view) { return view.equals(Range.all()) ? this : new SubRangeSet(view); } private final class SubRangeSet extends TreeRangeSet { private final Range restriction; SubRangeSet(Range restriction) { super(new SubRangeSetRangesByLowerBound(Range.>all(), restriction, TreeRangeSet.this.rangesByLowerBound)); this.restriction = restriction; } @Override public boolean encloses(Range range) { if (!restriction.isEmpty() && restriction.encloses(range)) { Range enclosing = TreeRangeSet.this.rangeEnclosing(range); return enclosing != null && !enclosing.intersection(restriction).isEmpty(); } return false; } @Override @Nullable public Range rangeContaining(C value) { if (!restriction.contains(value)) { return null; } Range result = TreeRangeSet.this.rangeContaining(value); return (result == null) ? null : result.intersection(restriction); } @Override public void add(Range rangeToAdd) { checkArgument(restriction.encloses(rangeToAdd), "Cannot add range %s to subRangeSet(%s)", rangeToAdd, restriction); super.add(rangeToAdd); } @Override public void remove(Range rangeToRemove) { if (rangeToRemove.isConnected(restriction)) { TreeRangeSet.this.remove(rangeToRemove.intersection(restriction)); } } @Override public boolean contains(C value) { return restriction.contains(value) && TreeRangeSet.this.contains(value); } @Override public void clear() { TreeRangeSet.this.remove(restriction); } @Override public RangeSet subRangeSet(Range view) { if (view.encloses(restriction)) { return this; } else if (view.isConnected(restriction)) { return new SubRangeSet(restriction.intersection(view)); } else { return ImmutableRangeSet.of(); } } } }