297 lines
9.2 KiB
Java
297 lines
9.2 KiB
Java
|
/*
|
||
|
* 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.checkElementIndex;
|
||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||
|
|
||
|
import java.util.Map;
|
||
|
import java.util.Map.Entry;
|
||
|
import java.util.NoSuchElementException;
|
||
|
|
||
|
import javax.annotation.Nullable;
|
||
|
|
||
|
import com.google.common.annotations.Beta;
|
||
|
import com.google.common.annotations.GwtIncompatible;
|
||
|
import com.google.common.collect.SortedLists.KeyAbsentBehavior;
|
||
|
import com.google.common.collect.SortedLists.KeyPresentBehavior;
|
||
|
|
||
|
/**
|
||
|
* An immutable implementation of {@code RangeMap}, supporting all query
|
||
|
* operations efficiently.
|
||
|
*
|
||
|
* <p>
|
||
|
* Like all {@code RangeMap} implementations, this supports neither null keys
|
||
|
* nor null values.
|
||
|
*
|
||
|
* @author Louis Wasserman
|
||
|
* @since 14.0
|
||
|
*/
|
||
|
@Beta
|
||
|
@GwtIncompatible("NavigableMap")
|
||
|
public class ImmutableRangeMap<K extends Comparable<?>, V> implements RangeMap<K, V> {
|
||
|
|
||
|
private static final ImmutableRangeMap<Comparable<?>, Object> EMPTY = new ImmutableRangeMap<Comparable<?>, Object>(
|
||
|
ImmutableList.<Range<Comparable<?>>>of(), ImmutableList.of());
|
||
|
|
||
|
/**
|
||
|
* Returns an empty immutable range map.
|
||
|
*/
|
||
|
@SuppressWarnings("unchecked")
|
||
|
public static <K extends Comparable<?>, V> ImmutableRangeMap<K, V> of() {
|
||
|
return (ImmutableRangeMap<K, V>) EMPTY;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an immutable range map mapping a single range to a single value.
|
||
|
*/
|
||
|
public static <K extends Comparable<?>, V> ImmutableRangeMap<K, V> of(Range<K> range, V value) {
|
||
|
return new ImmutableRangeMap<K, V>(ImmutableList.of(range), ImmutableList.of(value));
|
||
|
}
|
||
|
|
||
|
@SuppressWarnings("unchecked")
|
||
|
public static <K extends Comparable<?>, V> ImmutableRangeMap<K, V> copyOf(RangeMap<K, ? extends V> rangeMap) {
|
||
|
if (rangeMap instanceof ImmutableRangeMap) {
|
||
|
return (ImmutableRangeMap<K, V>) rangeMap;
|
||
|
}
|
||
|
Map<Range<K>, ? extends V> map = rangeMap.asMapOfRanges();
|
||
|
ImmutableList.Builder<Range<K>> rangesBuilder = new ImmutableList.Builder<Range<K>>(map.size());
|
||
|
ImmutableList.Builder<V> valuesBuilder = new ImmutableList.Builder<V>(map.size());
|
||
|
for (Entry<Range<K>, ? extends V> entry : map.entrySet()) {
|
||
|
rangesBuilder.add(entry.getKey());
|
||
|
valuesBuilder.add(entry.getValue());
|
||
|
}
|
||
|
return new ImmutableRangeMap<K, V>(rangesBuilder.build(), valuesBuilder.build());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a new builder for an immutable range map.
|
||
|
*/
|
||
|
public static <K extends Comparable<?>, V> Builder<K, V> builder() {
|
||
|
return new Builder<K, V>();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A builder for immutable range maps. Overlapping ranges are prohibited.
|
||
|
*/
|
||
|
public static final class Builder<K extends Comparable<?>, V> {
|
||
|
private final RangeSet<K> keyRanges;
|
||
|
private final RangeMap<K, V> rangeMap;
|
||
|
|
||
|
public Builder() {
|
||
|
this.keyRanges = TreeRangeSet.create();
|
||
|
this.rangeMap = TreeRangeMap.create();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Associates the specified range with the specified value.
|
||
|
*
|
||
|
* @throws IllegalArgumentException if {@code range} overlaps with any other
|
||
|
* ranges inserted into this builder, or if
|
||
|
* {@code range} is empty
|
||
|
*/
|
||
|
public Builder<K, V> put(Range<K> range, V value) {
|
||
|
checkNotNull(range);
|
||
|
checkNotNull(value);
|
||
|
checkArgument(!range.isEmpty(), "Range must not be empty, but was %s", range);
|
||
|
if (!keyRanges.complement().encloses(range)) {
|
||
|
// it's an error case; we can afford an expensive lookup
|
||
|
for (Entry<Range<K>, V> entry : rangeMap.asMapOfRanges().entrySet()) {
|
||
|
Range<K> key = entry.getKey();
|
||
|
if (key.isConnected(range) && !key.intersection(range).isEmpty()) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Overlapping ranges: range " + range + " overlaps with entry " + entry);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
keyRanges.add(range);
|
||
|
rangeMap.put(range, value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copies all associations from the specified range map into this builder.
|
||
|
*
|
||
|
* @throws IllegalArgumentException if any of the ranges in {@code rangeMap}
|
||
|
* overlap with ranges already in this builder
|
||
|
*/
|
||
|
public Builder<K, V> putAll(RangeMap<K, ? extends V> rangeMap) {
|
||
|
for (Entry<Range<K>, ? extends V> entry : rangeMap.asMapOfRanges().entrySet()) {
|
||
|
put(entry.getKey(), entry.getValue());
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an {@code ImmutableRangeMap} containing the associations previously
|
||
|
* added to this builder.
|
||
|
*/
|
||
|
public ImmutableRangeMap<K, V> build() {
|
||
|
Map<Range<K>, V> map = rangeMap.asMapOfRanges();
|
||
|
ImmutableList.Builder<Range<K>> rangesBuilder = new ImmutableList.Builder<Range<K>>(map.size());
|
||
|
ImmutableList.Builder<V> valuesBuilder = new ImmutableList.Builder<V>(map.size());
|
||
|
for (Entry<Range<K>, V> entry : map.entrySet()) {
|
||
|
rangesBuilder.add(entry.getKey());
|
||
|
valuesBuilder.add(entry.getValue());
|
||
|
}
|
||
|
return new ImmutableRangeMap<K, V>(rangesBuilder.build(), valuesBuilder.build());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private final ImmutableList<Range<K>> ranges;
|
||
|
private final ImmutableList<V> values;
|
||
|
|
||
|
ImmutableRangeMap(ImmutableList<Range<K>> ranges, ImmutableList<V> values) {
|
||
|
this.ranges = ranges;
|
||
|
this.values = values;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@Nullable
|
||
|
public V get(K key) {
|
||
|
int index = SortedLists.binarySearch(ranges, Range.<K>lowerBoundFn(), Cut.belowValue(key),
|
||
|
KeyPresentBehavior.ANY_PRESENT, KeyAbsentBehavior.NEXT_LOWER);
|
||
|
if (index == -1) {
|
||
|
return null;
|
||
|
} else {
|
||
|
Range<K> range = ranges.get(index);
|
||
|
return range.contains(key) ? values.get(index) : null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@Nullable
|
||
|
public Map.Entry<Range<K>, V> getEntry(K key) {
|
||
|
int index = SortedLists.binarySearch(ranges, Range.<K>lowerBoundFn(), Cut.belowValue(key),
|
||
|
KeyPresentBehavior.ANY_PRESENT, KeyAbsentBehavior.NEXT_LOWER);
|
||
|
if (index == -1) {
|
||
|
return null;
|
||
|
} else {
|
||
|
Range<K> range = ranges.get(index);
|
||
|
return range.contains(key) ? Maps.immutableEntry(range, values.get(index)) : null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Range<K> span() {
|
||
|
if (ranges.isEmpty()) {
|
||
|
throw new NoSuchElementException();
|
||
|
}
|
||
|
Range<K> firstRange = ranges.get(0);
|
||
|
Range<K> lastRange = ranges.get(ranges.size() - 1);
|
||
|
return Range.create(firstRange.lowerBound, lastRange.upperBound);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void put(Range<K> range, V value) {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void putAll(RangeMap<K, V> rangeMap) {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void clear() {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void remove(Range<K> range) {
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ImmutableMap<Range<K>, V> asMapOfRanges() {
|
||
|
if (ranges.isEmpty()) {
|
||
|
return ImmutableMap.of();
|
||
|
}
|
||
|
RegularImmutableSortedSet<Range<K>> rangeSet = new RegularImmutableSortedSet<Range<K>>(ranges,
|
||
|
Range.RANGE_LEX_ORDERING);
|
||
|
return new RegularImmutableSortedMap<Range<K>, V>(rangeSet, values);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ImmutableRangeMap<K, V> subRangeMap(final Range<K> range) {
|
||
|
if (checkNotNull(range).isEmpty()) {
|
||
|
return ImmutableRangeMap.of();
|
||
|
} else if (ranges.isEmpty() || range.encloses(span())) {
|
||
|
return this;
|
||
|
}
|
||
|
int lowerIndex = SortedLists.binarySearch(ranges, Range.<K>upperBoundFn(), range.lowerBound,
|
||
|
KeyPresentBehavior.FIRST_AFTER, KeyAbsentBehavior.NEXT_HIGHER);
|
||
|
int upperIndex = SortedLists.binarySearch(ranges, Range.<K>lowerBoundFn(), range.upperBound,
|
||
|
KeyPresentBehavior.ANY_PRESENT, KeyAbsentBehavior.NEXT_HIGHER);
|
||
|
if (lowerIndex >= upperIndex) {
|
||
|
return ImmutableRangeMap.of();
|
||
|
}
|
||
|
final int off = lowerIndex;
|
||
|
final int len = upperIndex - lowerIndex;
|
||
|
ImmutableList<Range<K>> subRanges = new ImmutableList<Range<K>>() {
|
||
|
@Override
|
||
|
public int size() {
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Range<K> get(int index) {
|
||
|
checkElementIndex(index, len);
|
||
|
if (index == 0 || index == len - 1) {
|
||
|
return ranges.get(index + off).intersection(range);
|
||
|
} else {
|
||
|
return ranges.get(index + off);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
boolean isPartialView() {
|
||
|
return true;
|
||
|
}
|
||
|
};
|
||
|
final ImmutableRangeMap<K, V> outer = this;
|
||
|
return new ImmutableRangeMap<K, V>(subRanges, values.subList(lowerIndex, upperIndex)) {
|
||
|
@Override
|
||
|
public ImmutableRangeMap<K, V> subRangeMap(Range<K> subRange) {
|
||
|
if (range.isConnected(subRange)) {
|
||
|
return outer.subRangeMap(subRange.intersection(range));
|
||
|
} else {
|
||
|
return ImmutableRangeMap.of();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return asMapOfRanges().hashCode();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(@Nullable Object o) {
|
||
|
if (o instanceof RangeMap) {
|
||
|
RangeMap<?, ?> rangeMap = (RangeMap<?, ?>) o;
|
||
|
return asMapOfRanges().equals(rangeMap.asMapOfRanges());
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String toString() {
|
||
|
return asMapOfRanges().toString();
|
||
|
}
|
||
|
}
|