All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.TreeRangeMap Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * 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 static java.util.Objects.requireNonNull;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps.IteratorBasedAbstractMap;
import java.util.AbstractMap;
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 java.util.function.BiFunction;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * An implementation of {@code RangeMap} based on a {@code TreeMap}, supporting all optional
 * operations.
 *
 * 

Like all {@code RangeMap} implementations, this supports neither null keys nor null values. * * @author Louis Wasserman * @since 14.0 */ @GwtIncompatible // NavigableMap @ElementTypesAreNonnullByDefault public final class TreeRangeMap implements RangeMap { private final NavigableMap, RangeMapEntry> entriesByLowerBound; public static TreeRangeMap create() { return new TreeRangeMap<>(); } private TreeRangeMap() { this.entriesByLowerBound = Maps.newTreeMap(); } private static final class RangeMapEntry extends AbstractMapEntry, V> { private final Range range; private final V value; RangeMapEntry(Cut lowerBound, Cut upperBound, V value) { this(Range.create(lowerBound, upperBound), value); } RangeMapEntry(Range range, V value) { this.range = range; this.value = value; } @Override public Range getKey() { return range; } @Override public V getValue() { return value; } public boolean contains(K value) { return range.contains(value); } Cut getLowerBound() { return range.lowerBound; } Cut getUpperBound() { return range.upperBound; } } @Override @CheckForNull public V get(K key) { Entry, V> entry = getEntry(key); return (entry == null) ? null : entry.getValue(); } @Override @CheckForNull public Entry, V> getEntry(K key) { Entry, RangeMapEntry> mapEntry = entriesByLowerBound.floorEntry(Cut.belowValue(key)); if (mapEntry != null && mapEntry.getValue().contains(key)) { return mapEntry.getValue(); } else { return null; } } @Override public void put(Range range, V value) { if (!range.isEmpty()) { checkNotNull(value); remove(range); entriesByLowerBound.put(range.lowerBound, new RangeMapEntry(range, value)); } } @Override public void putCoalescing(Range range, V value) { // don't short-circuit if the range is empty - it may be between two ranges we can coalesce. if (entriesByLowerBound.isEmpty()) { put(range, value); return; } Range coalescedRange = coalescedRange(range, checkNotNull(value)); put(coalescedRange, value); } /** Computes the coalesced range for the given range+value - does not mutate the map. */ private Range coalescedRange(Range range, V value) { Range coalescedRange = range; Entry, RangeMapEntry> lowerEntry = entriesByLowerBound.lowerEntry(range.lowerBound); coalescedRange = coalesce(coalescedRange, value, lowerEntry); Entry, RangeMapEntry> higherEntry = entriesByLowerBound.floorEntry(range.upperBound); coalescedRange = coalesce(coalescedRange, value, higherEntry); return coalescedRange; } /** Returns the range that spans the given range and entry, if the entry can be coalesced. */ private static Range coalesce( Range range, V value, @CheckForNull Entry, RangeMapEntry> entry) { if (entry != null && entry.getValue().getKey().isConnected(range) && entry.getValue().getValue().equals(value)) { return range.span(entry.getValue().getKey()); } return range; } @Override public void putAll(RangeMap rangeMap) { for (Entry, ? extends V> entry : rangeMap.asMapOfRanges().entrySet()) { put(entry.getKey(), entry.getValue()); } } @Override public void clear() { entriesByLowerBound.clear(); } @Override public Range span() { Entry, RangeMapEntry> firstEntry = entriesByLowerBound.firstEntry(); Entry, RangeMapEntry> lastEntry = entriesByLowerBound.lastEntry(); // Either both are null or neither is, but we check both to satisfy the nullness checker. if (firstEntry == null || lastEntry == null) { throw new NoSuchElementException(); } return Range.create( firstEntry.getValue().getKey().lowerBound, lastEntry.getValue().getKey().upperBound); } private void putRangeMapEntry(Cut lowerBound, Cut upperBound, V value) { entriesByLowerBound.put(lowerBound, new RangeMapEntry(lowerBound, upperBound, value)); } @Override public void remove(Range 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. */ Entry, RangeMapEntry> mapEntryBelowToTruncate = entriesByLowerBound.lowerEntry(rangeToRemove.lowerBound); if (mapEntryBelowToTruncate != null) { // we know ( [ RangeMapEntry 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()); } } Entry, RangeMapEntry> mapEntryAboveToTruncate = entriesByLowerBound.lowerEntry(rangeToRemove.upperBound); if (mapEntryAboveToTruncate != null) { // we know ( ] RangeMapEntry 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.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear(); } private void split(Cut cut) { /* * The comments for this method will use | to indicate the cut point and ( ) to indicate the * bounds of ranges in the range map. */ Entry, RangeMapEntry> mapEntryToSplit = entriesByLowerBound.lowerEntry(cut); if (mapEntryToSplit == null) { return; } // we know ( | RangeMapEntry rangeMapEntry = mapEntryToSplit.getValue(); if (rangeMapEntry.getUpperBound().compareTo(cut) <= 0) { return; } // we know ( | ) putRangeMapEntry(rangeMapEntry.getLowerBound(), cut, rangeMapEntry.getValue()); putRangeMapEntry(cut, rangeMapEntry.getUpperBound(), rangeMapEntry.getValue()); } @Override public void merge( Range range, @CheckForNull V value, BiFunction remappingFunction) { checkNotNull(range); checkNotNull(remappingFunction); if (range.isEmpty()) { return; } split(range.lowerBound); split(range.upperBound); // Due to the splitting of any entries spanning the range bounds, we know that any entry with a // lower bound in the merge range is entirely contained by the merge range. Set, RangeMapEntry>> entriesInMergeRange = entriesByLowerBound.subMap(range.lowerBound, range.upperBound).entrySet(); // Create entries mapping any unmapped ranges in the merge range to the specified value. ImmutableMap.Builder, RangeMapEntry> gaps = ImmutableMap.builder(); if (value != null) { final Iterator, RangeMapEntry>> backingItr = entriesInMergeRange.iterator(); Cut lowerBound = range.lowerBound; while (backingItr.hasNext()) { RangeMapEntry entry = backingItr.next().getValue(); Cut upperBound = entry.getLowerBound(); if (!lowerBound.equals(upperBound)) { gaps.put(lowerBound, new RangeMapEntry(lowerBound, upperBound, value)); } lowerBound = entry.getUpperBound(); } if (!lowerBound.equals(range.upperBound)) { gaps.put(lowerBound, new RangeMapEntry(lowerBound, range.upperBound, value)); } } // Remap all existing entries in the merge range. final Iterator, RangeMapEntry>> backingItr = entriesInMergeRange.iterator(); while (backingItr.hasNext()) { Entry, RangeMapEntry> entry = backingItr.next(); V newValue = remappingFunction.apply(entry.getValue().getValue(), value); if (newValue == null) { backingItr.remove(); } else { entry.setValue( new RangeMapEntry( entry.getValue().getLowerBound(), entry.getValue().getUpperBound(), newValue)); } } entriesByLowerBound.putAll(gaps.build()); } @Override public Map, V> asMapOfRanges() { return new AsMapOfRanges(entriesByLowerBound.values()); } @Override public Map, V> asDescendingMapOfRanges() { return new AsMapOfRanges(entriesByLowerBound.descendingMap().values()); } private final class AsMapOfRanges extends IteratorBasedAbstractMap, V> { final Iterable, V>> entryIterable; @SuppressWarnings("unchecked") // it's safe to upcast iterables AsMapOfRanges(Iterable> entryIterable) { this.entryIterable = (Iterable) entryIterable; } @Override public boolean containsKey(@CheckForNull Object key) { return get(key) != null; } @Override @CheckForNull public V get(@CheckForNull Object key) { if (key instanceof Range) { Range range = (Range) key; RangeMapEntry rangeMapEntry = entriesByLowerBound.get(range.lowerBound); if (rangeMapEntry != null && rangeMapEntry.getKey().equals(range)) { return rangeMapEntry.getValue(); } } return null; } @Override public int size() { return entriesByLowerBound.size(); } @Override Iterator, V>> entryIterator() { return entryIterable.iterator(); } } @Override public RangeMap subRangeMap(Range subRange) { if (subRange.equals(Range.all())) { return this; } else { return new SubRangeMap(subRange); } } @SuppressWarnings("unchecked") private RangeMap emptySubRangeMap() { return (RangeMap) (RangeMap) EMPTY_SUB_RANGE_MAP; } @SuppressWarnings("ConstantCaseForConstants") // This RangeMap is immutable. private static final RangeMap, Object> EMPTY_SUB_RANGE_MAP = new RangeMap, Object>() { @Override @CheckForNull public Object get(Comparable key) { return null; } @Override @CheckForNull public Entry>, 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 putCoalescing(Range> range, Object value) { checkNotNull(range); throw new IllegalArgumentException( "Cannot insert range " + range + " into an empty subRangeMap"); } @Override public void putAll(RangeMap, ? extends Object> 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 void merge( Range> range, @CheckForNull Object value, BiFunction remappingFunction) { checkNotNull(range); throw new IllegalArgumentException( "Cannot merge range " + range + " into an empty subRangeMap"); } @Override public Map>, Object> asMapOfRanges() { return Collections.emptyMap(); } @Override public Map>, Object> asDescendingMapOfRanges() { return Collections.emptyMap(); } @Override public RangeMap, Object> subRangeMap(Range> range) { checkNotNull(range); return this; } }; private class SubRangeMap implements RangeMap { private final Range subRange; SubRangeMap(Range subRange) { this.subRange = subRange; } @Override @CheckForNull public V get(K key) { return subRange.contains(key) ? TreeRangeMap.this.get(key) : null; } @Override @CheckForNull public Entry, V> getEntry(K key) { if (subRange.contains(key)) { Entry, V> entry = TreeRangeMap.this.getEntry(key); if (entry != null) { return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); } } return null; } @Override public Range span() { Cut lowerBound; Entry, RangeMapEntry> 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 upperBound; Entry, RangeMapEntry> 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 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 putCoalescing(Range range, V value) { if (entriesByLowerBound.isEmpty() || !subRange.encloses(range)) { put(range, value); return; } Range coalescedRange = coalescedRange(range, checkNotNull(value)); // only coalesce ranges within the subRange put(coalescedRange.intersection(subRange), value); } @Override public void putAll(RangeMap rangeMap) { if (rangeMap.asMapOfRanges().isEmpty()) { return; } Range 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 range) { if (range.isConnected(subRange)) { TreeRangeMap.this.remove(range.intersection(subRange)); } } @Override public void merge( Range range, @CheckForNull V value, BiFunction remappingFunction) { checkArgument( subRange.encloses(range), "Cannot merge range %s into a subRangeMap(%s)", range, subRange); TreeRangeMap.this.merge(range, value, remappingFunction); } @Override public RangeMap subRangeMap(Range range) { if (!range.isConnected(subRange)) { return emptySubRangeMap(); } else { return TreeRangeMap.this.subRangeMap(range.intersection(subRange)); } } @Override public Map, V> asMapOfRanges() { return new SubRangeMapAsMap(); } @Override public Map, V> asDescendingMapOfRanges() { return new SubRangeMapAsMap() { @Override Iterator, V>> entryIterator() { if (subRange.isEmpty()) { return Iterators.emptyIterator(); } final Iterator> backingItr = entriesByLowerBound .headMap(subRange.upperBound, false) .descendingMap() .values() .iterator(); return new AbstractIterator, V>>() { @Override @CheckForNull protected Entry, V> computeNext() { if (backingItr.hasNext()) { RangeMapEntry entry = backingItr.next(); if (entry.getUpperBound().compareTo(subRange.lowerBound) <= 0) { return endOfData(); } return Maps.immutableEntry(entry.getKey().intersection(subRange), entry.getValue()); } return endOfData(); } }; } }; } @Override public boolean equals(@CheckForNull 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, V> { @Override public boolean containsKey(@CheckForNull Object key) { return get(key) != null; } @Override @CheckForNull public V get(@CheckForNull Object key) { try { if (key instanceof Range) { @SuppressWarnings("unchecked") // we catch ClassCastExceptions Range r = (Range) key; if (!subRange.encloses(r) || r.isEmpty()) { return null; } RangeMapEntry candidate = null; if (r.lowerBound.compareTo(subRange.lowerBound) == 0) { // r could be truncated on the left Entry, RangeMapEntry> 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 @CheckForNull public V remove(@CheckForNull Object key) { V value = get(key); if (value != null) { // it's definitely in the map, so the cast and requireNonNull are safe @SuppressWarnings("unchecked") Range range = (Range) requireNonNull(key); TreeRangeMap.this.remove(range); return value; } return null; } @Override public void clear() { SubRangeMap.this.clear(); } private boolean removeEntryIf(Predicate, V>> predicate) { List> toRemove = Lists.newArrayList(); for (Entry, V> entry : entrySet()) { if (predicate.apply(entry)) { toRemove.add(entry.getKey()); } } for (Range range : toRemove) { TreeRangeMap.this.remove(range); } return !toRemove.isEmpty(); } @Override public Set> keySet() { return new Maps.KeySet, V>(SubRangeMapAsMap.this) { @Override public boolean remove(@CheckForNull Object o) { return SubRangeMapAsMap.this.remove(o) != null; } @Override public boolean retainAll(Collection c) { return removeEntryIf(compose(not(in(c)), Maps.>keyFunction())); } }; } @Override public Set, V>> entrySet() { return new Maps.EntrySet, V>() { @Override Map, V> map() { return SubRangeMapAsMap.this; } @Override public Iterator, V>> iterator() { return entryIterator(); } @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(); } }; } Iterator, V>> entryIterator() { if (subRange.isEmpty()) { return Iterators.emptyIterator(); } Cut cutToStart = MoreObjects.firstNonNull( entriesByLowerBound.floorKey(subRange.lowerBound), subRange.lowerBound); final Iterator> backingItr = entriesByLowerBound.tailMap(cutToStart, true).values().iterator(); return new AbstractIterator, V>>() { @Override @CheckForNull protected Entry, V> computeNext() { while (backingItr.hasNext()) { RangeMapEntry entry = backingItr.next(); if (entry.getLowerBound().compareTo(subRange.upperBound) >= 0) { return endOfData(); } 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 Collection values() { return new Maps.Values, V>(this) { @Override public boolean removeAll(Collection c) { return removeEntryIf(compose(in(c), Maps.valueFunction())); } @Override public boolean retainAll(Collection c) { return removeEntryIf(compose(not(in(c)), Maps.valueFunction())); } }; } } } @Override public boolean equals(@CheckForNull 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(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy