com.google.common.collect.ImmutableRangeMap Maven / Gradle / Ivy
/*
* 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 com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import com.google.common.collect.SortedLists.KeyAbsentBehavior;
import com.google.common.collect.SortedLists.KeyPresentBehavior;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.annotations.DoNotMock;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collector;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A {@link RangeMap} whose contents will never change, with many other important properties
* detailed at {@link ImmutableCollection}.
*
* @author Louis Wasserman
* @since 14.0
*/
@GwtIncompatible // NavigableMap
@ElementTypesAreNonnullByDefault
public class ImmutableRangeMap, V> implements RangeMap, Serializable {
private static final ImmutableRangeMap, Object> EMPTY =
new ImmutableRangeMap<>(ImmutableList.>>of(), ImmutableList.of());
/**
* Returns a {@code Collector} that accumulates the input elements into a new {@code
* ImmutableRangeMap}. As in {@link Builder}, overlapping ranges are not permitted.
*
* @since 23.1
*/
public static , V>
Collector> toImmutableRangeMap(
Function super T, Range> keyFunction,
Function super T, ? extends V> valueFunction) {
return CollectCollectors.toImmutableRangeMap(keyFunction, valueFunction);
}
/**
* Returns an empty immutable range map.
*
* Performance note: the instance returned is a singleton.
*/
@SuppressWarnings("unchecked")
public static , V> ImmutableRangeMap of() {
return (ImmutableRangeMap) EMPTY;
}
/** Returns an immutable range map mapping a single range to a single value. */
public static , V> ImmutableRangeMap of(Range range, V value) {
return new ImmutableRangeMap<>(ImmutableList.of(range), ImmutableList.of(value));
}
@SuppressWarnings("unchecked")
public static , V> ImmutableRangeMap copyOf(
RangeMap rangeMap) {
if (rangeMap instanceof ImmutableRangeMap) {
return (ImmutableRangeMap) rangeMap;
}
Map, ? extends V> map = rangeMap.asMapOfRanges();
ImmutableList.Builder> rangesBuilder = new ImmutableList.Builder<>(map.size());
ImmutableList.Builder valuesBuilder = new ImmutableList.Builder<>(map.size());
for (Entry, ? extends V> entry : map.entrySet()) {
rangesBuilder.add(entry.getKey());
valuesBuilder.add(entry.getValue());
}
return new ImmutableRangeMap<>(rangesBuilder.build(), valuesBuilder.build());
}
/** Returns a new builder for an immutable range map. */
public static , V> Builder builder() {
return new Builder<>();
}
/**
* A builder for immutable range maps. Overlapping ranges are prohibited.
*
* @since 14.0
*/
@DoNotMock
public static final class Builder, V> {
private final List, V>> entries;
public Builder() {
this.entries = Lists.newArrayList();
}
/**
* Associates the specified range with the specified value.
*
* @throws IllegalArgumentException if {@code range} is empty
*/
@CanIgnoreReturnValue
public Builder put(Range range, V value) {
checkNotNull(range);
checkNotNull(value);
checkArgument(!range.isEmpty(), "Range must not be empty, but was %s", range);
entries.add(Maps.immutableEntry(range, value));
return this;
}
/** Copies all associations from the specified range map into this builder. */
@CanIgnoreReturnValue
public Builder putAll(RangeMap rangeMap) {
for (Entry, ? extends V> entry : rangeMap.asMapOfRanges().entrySet()) {
put(entry.getKey(), entry.getValue());
}
return this;
}
@CanIgnoreReturnValue
Builder combine(Builder builder) {
entries.addAll(builder.entries);
return this;
}
/**
* Returns an {@code ImmutableRangeMap} containing the associations previously added to this
* builder.
*
* @throws IllegalArgumentException if any two ranges inserted into this builder overlap
*/
public ImmutableRangeMap build() {
Collections.sort(entries, Range.rangeLexOrdering().onKeys());
ImmutableList.Builder> rangesBuilder = new ImmutableList.Builder<>(entries.size());
ImmutableList.Builder valuesBuilder = new ImmutableList.Builder<>(entries.size());
for (int i = 0; i < entries.size(); i++) {
Range range = entries.get(i).getKey();
if (i > 0) {
Range prevRange = entries.get(i - 1).getKey();
if (range.isConnected(prevRange) && !range.intersection(prevRange).isEmpty()) {
throw new IllegalArgumentException(
"Overlapping ranges: range " + prevRange + " overlaps with entry " + range);
}
}
rangesBuilder.add(range);
valuesBuilder.add(entries.get(i).getValue());
}
return new ImmutableRangeMap<>(rangesBuilder.build(), valuesBuilder.build());
}
}
private final transient ImmutableList> ranges;
private final transient ImmutableList values;
ImmutableRangeMap(ImmutableList> ranges, ImmutableList values) {
this.ranges = ranges;
this.values = values;
}
@Override
@CheckForNull
public V get(K key) {
int index =
SortedLists.binarySearch(
ranges,
Range::lowerBound,
Cut.belowValue(key),
KeyPresentBehavior.ANY_PRESENT,
KeyAbsentBehavior.NEXT_LOWER);
if (index == -1) {
return null;
} else {
Range range = ranges.get(index);
return range.contains(key) ? values.get(index) : null;
}
}
@Override
@CheckForNull
public Entry, V> getEntry(K key) {
int index =
SortedLists.binarySearch(
ranges,
Range::lowerBound,
Cut.belowValue(key),
KeyPresentBehavior.ANY_PRESENT,
KeyAbsentBehavior.NEXT_LOWER);
if (index == -1) {
return null;
} else {
Range range = ranges.get(index);
return range.contains(key) ? Maps.immutableEntry(range, values.get(index)) : null;
}
}
@Override
public Range span() {
if (ranges.isEmpty()) {
throw new NoSuchElementException();
}
Range firstRange = ranges.get(0);
Range lastRange = ranges.get(ranges.size() - 1);
return Range.create(firstRange.lowerBound, lastRange.upperBound);
}
/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@Deprecated
@Override
@DoNotCall("Always throws UnsupportedOperationException")
public final void put(Range range, V value) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@Deprecated
@Override
@DoNotCall("Always throws UnsupportedOperationException")
public final void putCoalescing(Range range, V value) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@Deprecated
@Override
@DoNotCall("Always throws UnsupportedOperationException")
public final void putAll(RangeMap rangeMap) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@Deprecated
@Override
@DoNotCall("Always throws UnsupportedOperationException")
public final void clear() {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@Deprecated
@Override
@DoNotCall("Always throws UnsupportedOperationException")
public final void remove(Range range) {
throw new UnsupportedOperationException();
}
/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
* @since 28.1
*/
@Deprecated
@Override
@DoNotCall("Always throws UnsupportedOperationException")
public final void merge(
Range range,
@CheckForNull V value,
BiFunction super V, ? super @Nullable V, ? extends @Nullable V> remappingFunction) {
throw new UnsupportedOperationException();
}
@Override
public ImmutableMap, V> asMapOfRanges() {
if (ranges.isEmpty()) {
return ImmutableMap.of();
}
RegularImmutableSortedSet> rangeSet =
new RegularImmutableSortedSet<>(ranges, Range.rangeLexOrdering());
return new ImmutableSortedMap<>(rangeSet, values);
}
@Override
public ImmutableMap, V> asDescendingMapOfRanges() {
if (ranges.isEmpty()) {
return ImmutableMap.of();
}
RegularImmutableSortedSet> rangeSet =
new RegularImmutableSortedSet<>(ranges.reverse(), Range.rangeLexOrdering().reverse());
return new ImmutableSortedMap<>(rangeSet, values.reverse());
}
@Override
public ImmutableRangeMap subRangeMap(final Range range) {
if (checkNotNull(range).isEmpty()) {
return ImmutableRangeMap.of();
} else if (ranges.isEmpty() || range.encloses(span())) {
return this;
}
int lowerIndex =
SortedLists.binarySearch(
ranges,
Range::upperBound,
range.lowerBound,
KeyPresentBehavior.FIRST_AFTER,
KeyAbsentBehavior.NEXT_HIGHER);
int upperIndex =
SortedLists.binarySearch(
ranges,
Range::lowerBound,
range.upperBound,
KeyPresentBehavior.ANY_PRESENT,
KeyAbsentBehavior.NEXT_HIGHER);
if (lowerIndex >= upperIndex) {
return ImmutableRangeMap.of();
}
final int off = lowerIndex;
final int len = upperIndex - lowerIndex;
ImmutableList> subRanges =
new ImmutableList>() {
@Override
public int size() {
return len;
}
@Override
public Range 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;
}
// redeclare to help optimizers with b/310253115
@SuppressWarnings("RedundantOverride")
@Override
@J2ktIncompatible // serialization
Object writeReplace() {
return super.writeReplace();
}
};
final ImmutableRangeMap outer = this;
return new ImmutableRangeMap(subRanges, values.subList(lowerIndex, upperIndex)) {
@Override
public ImmutableRangeMap subRangeMap(Range subRange) {
if (range.isConnected(subRange)) {
return outer.subRangeMap(subRange.intersection(range));
} else {
return ImmutableRangeMap.of();
}
}
// redeclare to help optimizers with b/310253115
@SuppressWarnings("RedundantOverride")
@Override
@J2ktIncompatible // serialization
Object writeReplace() {
return super.writeReplace();
}
};
}
@Override
public int hashCode() {
return asMapOfRanges().hashCode();
}
@Override
public boolean equals(@CheckForNull Object o) {
if (o instanceof RangeMap) {
RangeMap, ?> rangeMap = (RangeMap, ?>) o;
return asMapOfRanges().equals(rangeMap.asMapOfRanges());
}
return false;
}
@Override
public String toString() {
return asMapOfRanges().toString();
}
/**
* This class is used to serialize ImmutableRangeMap instances. Serializes the {@link
* #asMapOfRanges()} form.
*/
private static class SerializedForm, V> implements Serializable {
private final ImmutableMap, V> mapOfRanges;
SerializedForm(ImmutableMap, V> mapOfRanges) {
this.mapOfRanges = mapOfRanges;
}
Object readResolve() {
if (mapOfRanges.isEmpty()) {
return of();
} else {
return createRangeMap();
}
}
Object createRangeMap() {
Builder builder = new Builder<>();
for (Entry, V> entry : mapOfRanges.entrySet()) {
builder.put(entry.getKey(), entry.getValue());
}
return builder.build();
}
private static final long serialVersionUID = 0;
}
Object writeReplace() {
return new SerializedForm<>(asMapOfRanges());
}
@J2ktIncompatible // java.io.ObjectInputStream
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Use SerializedForm");
}
private static final long serialVersionUID = 0;
}