| /* |
| * Copyright (C) 2012 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 static com.google.common.base.Predicates.compose; |
| import static com.google.common.base.Predicates.in; |
| import static com.google.common.base.Predicates.not; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.annotations.GwtIncompatible; |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Predicate; |
| |
| import java.util.AbstractMap; |
| import java.util.AbstractSet; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.NavigableMap; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * An implementation of {@code RangeMap} based on a {@code TreeMap}, supporting |
| * all optional operations. |
| * |
| * <p>Like all {@code RangeMap} implementations, this supports neither null |
| * keys nor null values. |
| * |
| * @author Louis Wasserman |
| * @since 14.0 |
| */ |
| @Beta |
| @GwtIncompatible("NavigableMap") |
| public final class TreeRangeMap<K extends Comparable, V> implements RangeMap<K, V> { |
| |
| private final NavigableMap<Cut<K>, RangeMapEntry<K, V>> entriesByLowerBound; |
| |
| public static <K extends Comparable, V> TreeRangeMap<K, V> create() { |
| return new TreeRangeMap<K, V>(); |
| } |
| |
| private TreeRangeMap() { |
| this.entriesByLowerBound = Maps.newTreeMap(); |
| } |
| |
| private static final class RangeMapEntry<K extends Comparable, V> |
| extends AbstractMapEntry<Range<K>, V> { |
| private final Range<K> range; |
| private final V value; |
| |
| RangeMapEntry(Cut<K> lowerBound, Cut<K> upperBound, V value) { |
| this(Range.create(lowerBound, upperBound), value); |
| } |
| |
| RangeMapEntry(Range<K> range, V value) { |
| this.range = range; |
| this.value = value; |
| } |
| |
| @Override |
| public Range<K> getKey() { |
| return range; |
| } |
| |
| @Override |
| public V getValue() { |
| return value; |
| } |
| |
| public boolean contains(K value) { |
| return range.contains(value); |
| } |
| |
| Cut<K> getLowerBound() { |
| return range.lowerBound; |
| } |
| |
| Cut<K> getUpperBound() { |
| return range.upperBound; |
| } |
| } |
| |
| @Override |
| @Nullable |
| public V get(K key) { |
| Entry<Range<K>, V> entry = getEntry(key); |
| return (entry == null) ? null : entry.getValue(); |
| } |
| |
| @Override |
| @Nullable |
| public Entry<Range<K>, V> getEntry(K key) { |
| Map.Entry<Cut<K>, RangeMapEntry<K, V>> mapEntry = |
| entriesByLowerBound.floorEntry(Cut.belowValue(key)); |
| if (mapEntry != null && mapEntry.getValue().contains(key)) { |
| return mapEntry.getValue(); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public void put(Range<K> range, V value) { |
| if (!range.isEmpty()) { |
| checkNotNull(value); |
| remove(range); |
| entriesByLowerBound.put(range.lowerBound, new RangeMapEntry<K, V>(range, value)); |
| } |
| } |
| |
| @Override |
| public void putAll(RangeMap<K, V> rangeMap) { |
| for (Map.Entry<Range<K>, V> entry : rangeMap.asMapOfRanges().entrySet()) { |
| put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @Override |
| public void clear() { |
| entriesByLowerBound.clear(); |
| } |
| |
| @Override |
| public Range<K> span() { |
| Entry<Cut<K>, RangeMapEntry<K, V>> firstEntry = entriesByLowerBound.firstEntry(); |
| Entry<Cut<K>, RangeMapEntry<K, V>> lastEntry = entriesByLowerBound.lastEntry(); |
| if (firstEntry == null) { |
| throw new NoSuchElementException(); |
| } |
| return Range.create( |
| firstEntry.getValue().getKey().lowerBound, |
| lastEntry.getValue().getKey().upperBound); |
| } |
| |
| private void putRangeMapEntry(Cut<K> lowerBound, Cut<K> upperBound, V value) { |
| entriesByLowerBound.put(lowerBound, new RangeMapEntry<K, V>(lowerBound, upperBound, value)); |
| } |
| |
| @Override |
| public void remove(Range<K> rangeToRemove) { |
| if (rangeToRemove.isEmpty()) { |
| return; |
| } |
| |
| /* |
| * The comments for this method will use [ ] to indicate the bounds of rangeToRemove and ( ) to |
| * indicate the bounds of ranges in the range map. |
| */ |
| Map.Entry<Cut<K>, RangeMapEntry<K, V>> mapEntryBelowToTruncate = |
| entriesByLowerBound.lowerEntry(rangeToRemove.lowerBound); |
| if (mapEntryBelowToTruncate != null) { |
| // we know ( [ |
| RangeMapEntry<K, V> rangeMapEntry = mapEntryBelowToTruncate.getValue(); |
| if (rangeMapEntry.getUpperBound().compareTo(rangeToRemove.lowerBound) > 0) { |
| // we know ( [ ) |
| if (rangeMapEntry.getUpperBound().compareTo(rangeToRemove.upperBound) > 0) { |
| // we know ( [ ] ), so insert the range ] ) back into the map -- |
| // it's being split apart |
| putRangeMapEntry(rangeToRemove.upperBound, rangeMapEntry.getUpperBound(), |
| mapEntryBelowToTruncate.getValue().getValue()); |
| } |
| // overwrite mapEntryToTruncateBelow with a truncated range |
| putRangeMapEntry(rangeMapEntry.getLowerBound(), rangeToRemove.lowerBound, |
| mapEntryBelowToTruncate.getValue().getValue()); |
| } |
| } |
| |
| Map.Entry<Cut<K>, RangeMapEntry<K, V>> mapEntryAboveToTruncate = |
| entriesByLowerBound.lowerEntry(rangeToRemove.upperBound); |
| if (mapEntryAboveToTruncate != null) { |
| // we know ( ] |
| RangeMapEntry<K, V> rangeMapEntry = mapEntryAboveToTruncate.getValue(); |
| if (rangeMapEntry.getUpperBound().compareTo(rangeToRemove.upperBound) > 0) { |
| // we know ( ] ), and since we dealt with truncating below already, |
| // we know [ ( ] ) |
| putRangeMapEntry(rangeToRemove.upperBound, rangeMapEntry.getUpperBound(), |
| mapEntryAboveToTruncate.getValue().getValue()); |
| entriesByLowerBound.remove(rangeToRemove.lowerBound); |
| } |
| } |
| entriesByLowerBound.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear(); |
| } |
| |
| @Override |
| public Map<Range<K>, V> asMapOfRanges() { |
| return new AsMapOfRanges(); |
| } |
| |
| private final class AsMapOfRanges extends AbstractMap<Range<K>, V> { |
| |
| @Override |
| public boolean containsKey(@Nullable Object key) { |
| return get(key) != null; |
| } |
| |
| @Override |
| public V get(@Nullable Object key) { |
| if (key instanceof Range) { |
| Range<?> range = (Range<?>) key; |
| RangeMapEntry<K, V> rangeMapEntry = entriesByLowerBound.get(range.lowerBound); |
| if (rangeMapEntry != null && rangeMapEntry.getKey().equals(range)) { |
| return rangeMapEntry.getValue(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Set<Entry<Range<K>, V>> entrySet() { |
| return new AbstractSet<Entry<Range<K>, V>>() { |
| |
| @SuppressWarnings("unchecked") // it's safe to upcast iterators |
| @Override |
| public Iterator<Entry<Range<K>, V>> iterator() { |
| return (Iterator) entriesByLowerBound.values().iterator(); |
| } |
| |
| @Override |
| public int size() { |
| return entriesByLowerBound.size(); |
| } |
| }; |
| } |
| } |
| |
| @Override |
| public RangeMap<K, V> subRangeMap(Range<K> subRange) { |
| if (subRange.equals(Range.all())) { |
| return this; |
| } else { |
| return new SubRangeMap(subRange); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private RangeMap<K, V> emptySubRangeMap() { |
| return EMPTY_SUB_RANGE_MAP; |
| } |
| |
| private static final RangeMap EMPTY_SUB_RANGE_MAP = |
| new RangeMap() { |
| @Override |
| @Nullable |
| public Object get(Comparable key) { |
| return null; |
| } |
| |
| @Override |
| @Nullable |
| public Entry<Range, Object> getEntry(Comparable key) { |
| return null; |
| } |
| |
| @Override |
| public Range span() { |
| throw new NoSuchElementException(); |
| } |
| |
| @Override |
| public void put(Range range, Object value) { |
| checkNotNull(range); |
| throw new IllegalArgumentException( |
| "Cannot insert range " + range + " into an empty subRangeMap"); |
| } |
| |
| @Override |
| public void putAll(RangeMap rangeMap) { |
| if (!rangeMap.asMapOfRanges().isEmpty()) { |
| throw new IllegalArgumentException( |
| "Cannot putAll(nonEmptyRangeMap) into an empty " + "subRangeMap"); |
| } |
| } |
| |
| @Override |
| public void clear() {} |
| |
| @Override |
| public void remove(Range range) { |
| checkNotNull(range); |
| } |
| |
| @Override |
| public Map<Range, Object> asMapOfRanges() { |
| return Collections.emptyMap(); |
| } |
| |
| @Override |
| public RangeMap subRangeMap(Range range) { |
| checkNotNull(range); |
| return this; |
| } |
| }; |
| |
| private class SubRangeMap implements RangeMap<K, V> { |
| |
| private final Range<K> subRange; |
| |
| SubRangeMap(Range<K> subRange) { |
| this.subRange = subRange; |
| } |
| |
| @Override |
| @Nullable |
| public V get(K key) { |
| return subRange.contains(key) |
| ? TreeRangeMap.this.get(key) |
| : null; |
| } |
| |
| @Override |
| @Nullable |
| public Entry<Range<K>, V> getEntry(K key) { |
| if (subRange.contains(key)) { |
| Entry<Range<K>, V> entry = TreeRangeMap.this.getEntry(key); |
| if (entry != null) { |
| return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Range<K> span() { |
| Cut<K> lowerBound; |
| Entry<Cut<K>, RangeMapEntry<K, V>> lowerEntry = |
| entriesByLowerBound.floorEntry(subRange.lowerBound); |
| if (lowerEntry != null && |
| lowerEntry.getValue().getUpperBound().compareTo(subRange.lowerBound) > 0) { |
| lowerBound = subRange.lowerBound; |
| } else { |
| lowerBound = entriesByLowerBound.ceilingKey(subRange.lowerBound); |
| if (lowerBound == null || lowerBound.compareTo(subRange.upperBound) >= 0) { |
| throw new NoSuchElementException(); |
| } |
| } |
| |
| Cut<K> upperBound; |
| Entry<Cut<K>, RangeMapEntry<K, V>> upperEntry = |
| entriesByLowerBound.lowerEntry(subRange.upperBound); |
| if (upperEntry == null) { |
| throw new NoSuchElementException(); |
| } else if (upperEntry.getValue().getUpperBound().compareTo(subRange.upperBound) >= 0) { |
| upperBound = subRange.upperBound; |
| } else { |
| upperBound = upperEntry.getValue().getUpperBound(); |
| } |
| return Range.create(lowerBound, upperBound); |
| } |
| |
| @Override |
| public void put(Range<K> range, V value) { |
| checkArgument(subRange.encloses(range), |
| "Cannot put range %s into a subRangeMap(%s)", range, subRange); |
| TreeRangeMap.this.put(range, value); |
| } |
| |
| @Override |
| public void putAll(RangeMap<K, V> rangeMap) { |
| if (rangeMap.asMapOfRanges().isEmpty()) { |
| return; |
| } |
| Range<K> span = rangeMap.span(); |
| checkArgument(subRange.encloses(span), |
| "Cannot putAll rangeMap with span %s into a subRangeMap(%s)", span, subRange); |
| TreeRangeMap.this.putAll(rangeMap); |
| } |
| |
| @Override |
| public void clear() { |
| TreeRangeMap.this.remove(subRange); |
| } |
| |
| @Override |
| public void remove(Range<K> range) { |
| if (range.isConnected(subRange)) { |
| TreeRangeMap.this.remove(range.intersection(subRange)); |
| } |
| } |
| |
| @Override |
| public RangeMap<K, V> subRangeMap(Range<K> range) { |
| if (!range.isConnected(subRange)) { |
| return emptySubRangeMap(); |
| } else { |
| return TreeRangeMap.this.subRangeMap(range.intersection(subRange)); |
| } |
| } |
| |
| @Override |
| public Map<Range<K>, V> asMapOfRanges() { |
| return new SubRangeMapAsMap(); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object o) { |
| if (o instanceof RangeMap) { |
| RangeMap<?, ?> rangeMap = (RangeMap<?, ?>) o; |
| return asMapOfRanges().equals(rangeMap.asMapOfRanges()); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return asMapOfRanges().hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| return asMapOfRanges().toString(); |
| } |
| |
| class SubRangeMapAsMap extends AbstractMap<Range<K>, V> { |
| |
| @Override |
| public boolean containsKey(Object key) { |
| return get(key) != null; |
| } |
| |
| @Override |
| public V get(Object key) { |
| try { |
| if (key instanceof Range) { |
| @SuppressWarnings("unchecked") // we catch ClassCastExceptions |
| Range<K> r = (Range<K>) key; |
| if (!subRange.encloses(r) || r.isEmpty()) { |
| return null; |
| } |
| RangeMapEntry<K, V> candidate = null; |
| if (r.lowerBound.compareTo(subRange.lowerBound) == 0) { |
| // r could be truncated on the left |
| Entry<Cut<K>, RangeMapEntry<K, V>> entry = |
| entriesByLowerBound.floorEntry(r.lowerBound); |
| if (entry != null) { |
| candidate = entry.getValue(); |
| } |
| } else { |
| candidate = entriesByLowerBound.get(r.lowerBound); |
| } |
| |
| if (candidate != null && candidate.getKey().isConnected(subRange) |
| && candidate.getKey().intersection(subRange).equals(r)) { |
| return candidate.getValue(); |
| } |
| } |
| } catch (ClassCastException e) { |
| return null; |
| } |
| return null; |
| } |
| |
| @Override |
| public V remove(Object key) { |
| V value = get(key); |
| if (value != null) { |
| @SuppressWarnings("unchecked") // it's definitely in the map, so safe |
| Range<K> range = (Range<K>) key; |
| TreeRangeMap.this.remove(range); |
| return value; |
| } |
| return null; |
| } |
| |
| @Override |
| public void clear() { |
| SubRangeMap.this.clear(); |
| } |
| |
| private boolean removeEntryIf(Predicate<? super Entry<Range<K>, V>> predicate) { |
| List<Range<K>> toRemove = Lists.newArrayList(); |
| for (Entry<Range<K>, V> entry : entrySet()) { |
| if (predicate.apply(entry)) { |
| toRemove.add(entry.getKey()); |
| } |
| } |
| for (Range<K> range : toRemove) { |
| TreeRangeMap.this.remove(range); |
| } |
| return !toRemove.isEmpty(); |
| } |
| |
| @Override |
| public Set<Range<K>> keySet() { |
| return new Maps.KeySet<Range<K>, V>(SubRangeMapAsMap.this) { |
| @Override |
| public boolean remove(@Nullable Object o) { |
| return SubRangeMapAsMap.this.remove(o) != null; |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> c) { |
| return removeEntryIf(compose(not(in(c)), Maps.<Range<K>>keyFunction())); |
| } |
| }; |
| } |
| |
| @Override |
| public Set<Entry<Range<K>, V>> entrySet() { |
| return new Maps.EntrySet<Range<K>, V>() { |
| @Override |
| Map<Range<K>, V> map() { |
| return SubRangeMapAsMap.this; |
| } |
| |
| @Override |
| public Iterator<Entry<Range<K>, V>> iterator() { |
| if (subRange.isEmpty()) { |
| return Iterators.emptyIterator(); |
| } |
| Cut<K> cutToStart = MoreObjects.firstNonNull( |
| entriesByLowerBound.floorKey(subRange.lowerBound), subRange.lowerBound); |
| final Iterator<RangeMapEntry<K, V>> backingItr = |
| entriesByLowerBound.tailMap(cutToStart, true).values().iterator(); |
| return new AbstractIterator<Entry<Range<K>, V>>() { |
| |
| @Override |
| protected Entry<Range<K>, V> computeNext() { |
| while (backingItr.hasNext()) { |
| RangeMapEntry<K, V> entry = backingItr.next(); |
| if (entry.getLowerBound().compareTo(subRange.upperBound) >= 0) { |
| break; |
| } else if (entry.getUpperBound().compareTo(subRange.lowerBound) > 0) { |
| // this might not be true e.g. at the start of the iteration |
| return Maps.immutableEntry( |
| entry.getKey().intersection(subRange), entry.getValue()); |
| } |
| } |
| return endOfData(); |
| } |
| }; |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> c) { |
| return removeEntryIf(not(in(c))); |
| } |
| |
| @Override |
| public int size() { |
| return Iterators.size(iterator()); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return !iterator().hasNext(); |
| } |
| }; |
| } |
| |
| @Override |
| public Collection<V> values() { |
| return new Maps.Values<Range<K>, V>(this) { |
| @Override |
| public boolean removeAll(Collection<?> c) { |
| return removeEntryIf(compose(in(c), Maps.<V>valueFunction())); |
| } |
| |
| @Override |
| public boolean retainAll(Collection<?> c) { |
| return removeEntryIf(compose(not(in(c)), Maps.<V>valueFunction())); |
| } |
| }; |
| } |
| } |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object o) { |
| if (o instanceof RangeMap) { |
| RangeMap<?, ?> rangeMap = (RangeMap<?, ?>) o; |
| return asMapOfRanges().equals(rangeMap.asMapOfRanges()); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return asMapOfRanges().hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| return entriesByLowerBound.values().toString(); |
| } |
| } |