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

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

There is a newer version: 1.45.6
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.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;

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;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.annotations.DoNotMock;
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 org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
 * A {@link RangeMap} whose contents will never change, with many other important properties
 * detailed at {@link ImmutableCollection}.
 *
 * @author Louis Wasserman
 * @since 14.0
 */
@Beta
@GwtIncompatible // NavigableMap
public class ImmutableRangeMap, V> implements RangeMap, Serializable {

  private static final ImmutableRangeMap, Object> EMPTY =
      new ImmutableRangeMap<>(ImmutableList.>>of(), ImmutableList.of());

  /** Returns an empty immutable range map. */
  @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
  @NullableDecl
  public V get(K key) {
    int index =
        SortedLists.binarySearch(
            ranges,
            Range.lowerBoundFn(),
            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
  @NullableDecl
  public Entry, V> getEntry(K key) {
    int index =
        SortedLists.binarySearch(
            ranges,
            Range.lowerBoundFn(),
            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();
  }

  @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.upperBoundFn(),
            range.lowerBound,
            KeyPresentBehavior.FIRST_AFTER,
            KeyAbsentBehavior.NEXT_HIGHER);
    int upperIndex =
        SortedLists.binarySearch(
            ranges,
            Range.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> 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;
          }
        };
    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();
        }
      }
    };
  }

  @Override
  public int hashCode() {
    return asMapOfRanges().hashCode();
  }

  @Override
  public boolean equals(@NullableDecl 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());
  }

  private static final long serialVersionUID = 0;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy