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

com.google.common.collect.ImmutableRangeSet 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.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_HIGHER;
import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_LOWER;
import static com.google.common.collect.SortedLists.KeyPresentBehavior.ANY_PRESENT;
import static java.util.Objects.requireNonNull;

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.common.primitives.Ints;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.annotations.concurrent.LazyInit;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Collector;
import javax.annotation.CheckForNull;

/**
 * A {@link RangeSet} whose contents will never change, with many other important properties
 * detailed at {@link ImmutableCollection}.
 *
 * @author Louis Wasserman
 * @since 14.0
 */
@GwtIncompatible
@ElementTypesAreNonnullByDefault
public final class ImmutableRangeSet extends AbstractRangeSet
    implements Serializable {

  private static final ImmutableRangeSet> EMPTY =
      new ImmutableRangeSet<>(ImmutableList.>>of());

  private static final ImmutableRangeSet> ALL =
      new ImmutableRangeSet<>(ImmutableList.of(Range.>all()));

  /**
   * Returns a {@code Collector} that accumulates the input elements into a new {@code
   * ImmutableRangeSet}. As in {@link Builder}, overlapping ranges are not permitted and adjacent
   * ranges will be merged.
   *
   * @since 23.1
   */
  public static >
      Collector, ?, ImmutableRangeSet> toImmutableRangeSet() {
    return CollectCollectors.toImmutableRangeSet();
  }

  /**
   * Returns an empty immutable range set.
   *
   * 

Performance note: the instance returned is a singleton. */ @SuppressWarnings("unchecked") public static ImmutableRangeSet of() { return (ImmutableRangeSet) EMPTY; } /** * Returns an immutable range set containing the specified single range. If {@link Range#isEmpty() * range.isEmpty()}, this is equivalent to {@link ImmutableRangeSet#of()}. */ public static ImmutableRangeSet of(Range range) { checkNotNull(range); if (range.isEmpty()) { return of(); } else if (range.equals(Range.all())) { return all(); } else { return new ImmutableRangeSet(ImmutableList.of(range)); } } /** Returns an immutable range set containing the single range {@link Range#all()}. */ @SuppressWarnings("unchecked") static ImmutableRangeSet all() { return (ImmutableRangeSet) ALL; } /** Returns an immutable copy of the specified {@code RangeSet}. */ public static ImmutableRangeSet copyOf(RangeSet rangeSet) { checkNotNull(rangeSet); if (rangeSet.isEmpty()) { return of(); } else if (rangeSet.encloses(Range.all())) { return all(); } if (rangeSet instanceof ImmutableRangeSet) { ImmutableRangeSet immutableRangeSet = (ImmutableRangeSet) rangeSet; if (!immutableRangeSet.isPartialView()) { return immutableRangeSet; } } return new ImmutableRangeSet(ImmutableList.copyOf(rangeSet.asRanges())); } /** * Returns an {@code ImmutableRangeSet} containing each of the specified disjoint ranges. * Overlapping ranges and empty ranges are forbidden, though adjacent ranges are permitted and * will be merged. * * @throws IllegalArgumentException if any ranges overlap or are empty * @since 21.0 */ public static > ImmutableRangeSet copyOf(Iterable> ranges) { return new ImmutableRangeSet.Builder().addAll(ranges).build(); } /** * Returns an {@code ImmutableRangeSet} representing the union of the specified ranges. * *

This is the smallest {@code RangeSet} which encloses each of the specified ranges. Duplicate * or connected ranges are permitted, and will be coalesced in the result. * * @since 21.0 */ public static > ImmutableRangeSet unionOf(Iterable> ranges) { return copyOf(TreeRangeSet.create(ranges)); } ImmutableRangeSet(ImmutableList> ranges) { this.ranges = ranges; } private ImmutableRangeSet(ImmutableList> ranges, ImmutableRangeSet complement) { this.ranges = ranges; this.complement = complement; } private final transient ImmutableList> ranges; @Override public boolean intersects(Range otherRange) { int ceilingIndex = SortedLists.binarySearch( ranges, Range.lowerBoundFn(), otherRange.lowerBound, Ordering.natural(), ANY_PRESENT, NEXT_HIGHER); if (ceilingIndex < ranges.size() && ranges.get(ceilingIndex).isConnected(otherRange) && !ranges.get(ceilingIndex).intersection(otherRange).isEmpty()) { return true; } return ceilingIndex > 0 && ranges.get(ceilingIndex - 1).isConnected(otherRange) && !ranges.get(ceilingIndex - 1).intersection(otherRange).isEmpty(); } @Override public boolean encloses(Range otherRange) { int index = SortedLists.binarySearch( ranges, Range.lowerBoundFn(), otherRange.lowerBound, Ordering.natural(), ANY_PRESENT, NEXT_LOWER); return index != -1 && ranges.get(index).encloses(otherRange); } @Override @CheckForNull public Range rangeContaining(C value) { int index = SortedLists.binarySearch( ranges, Range.lowerBoundFn(), Cut.belowValue(value), Ordering.natural(), ANY_PRESENT, NEXT_LOWER); if (index != -1) { Range range = ranges.get(index); return range.contains(value) ? range : null; } return null; } @Override public Range span() { if (ranges.isEmpty()) { throw new NoSuchElementException(); } return Range.create(ranges.get(0).lowerBound, ranges.get(ranges.size() - 1).upperBound); } @Override public boolean isEmpty() { return ranges.isEmpty(); } /** * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") public void add(Range range) { throw new UnsupportedOperationException(); } /** * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") public void addAll(RangeSet other) { throw new UnsupportedOperationException(); } /** * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") public void addAll(Iterable> other) { throw new UnsupportedOperationException(); } /** * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") public void remove(Range range) { throw new UnsupportedOperationException(); } /** * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") public void removeAll(RangeSet other) { throw new UnsupportedOperationException(); } /** * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */ @Deprecated @Override @DoNotCall("Always throws UnsupportedOperationException") public void removeAll(Iterable> other) { throw new UnsupportedOperationException(); } @Override public ImmutableSet> asRanges() { if (ranges.isEmpty()) { return ImmutableSet.of(); } return new RegularImmutableSortedSet<>(ranges, Range.rangeLexOrdering()); } @Override public ImmutableSet> asDescendingSetOfRanges() { if (ranges.isEmpty()) { return ImmutableSet.of(); } return new RegularImmutableSortedSet<>(ranges.reverse(), Range.rangeLexOrdering().reverse()); } @LazyInit @CheckForNull private transient ImmutableRangeSet complement; private final class ComplementRanges extends ImmutableList> { // True if the "positive" range set is empty or bounded below. private final boolean positiveBoundedBelow; // True if the "positive" range set is empty or bounded above. private final boolean positiveBoundedAbove; private final int size; ComplementRanges() { this.positiveBoundedBelow = ranges.get(0).hasLowerBound(); this.positiveBoundedAbove = Iterables.getLast(ranges).hasUpperBound(); int size = ranges.size() - 1; if (positiveBoundedBelow) { size++; } if (positiveBoundedAbove) { size++; } this.size = size; } @Override public int size() { return size; } @Override public Range get(int index) { checkElementIndex(index, size); Cut lowerBound; if (positiveBoundedBelow) { lowerBound = (index == 0) ? Cut.belowAll() : ranges.get(index - 1).upperBound; } else { lowerBound = ranges.get(index).upperBound; } Cut upperBound; if (positiveBoundedAbove && index == size - 1) { upperBound = Cut.aboveAll(); } else { upperBound = ranges.get(index + (positiveBoundedBelow ? 0 : 1)).lowerBound; } return Range.create(lowerBound, upperBound); } @Override boolean isPartialView() { return true; } } @Override public ImmutableRangeSet complement() { ImmutableRangeSet result = complement; if (result != null) { return result; } else if (ranges.isEmpty()) { return complement = all(); } else if (ranges.size() == 1 && ranges.get(0).equals(Range.all())) { return complement = of(); } else { ImmutableList> complementRanges = new ComplementRanges(); result = complement = new ImmutableRangeSet(complementRanges, this); } return result; } /** * Returns a new range set consisting of the union of this range set and {@code other}. * *

This is essentially the same as {@code TreeRangeSet.create(this).addAll(other)} except it * returns an {@code ImmutableRangeSet}. * * @since 21.0 */ public ImmutableRangeSet union(RangeSet other) { return unionOf(Iterables.concat(asRanges(), other.asRanges())); } /** * Returns a new range set consisting of the intersection of this range set and {@code other}. * *

This is essentially the same as {@code * TreeRangeSet.create(this).removeAll(other.complement())} except it returns an {@code * ImmutableRangeSet}. * * @since 21.0 */ public ImmutableRangeSet intersection(RangeSet other) { RangeSet copy = TreeRangeSet.create(this); copy.removeAll(other.complement()); return copyOf(copy); } /** * Returns a new range set consisting of the difference of this range set and {@code other}. * *

This is essentially the same as {@code TreeRangeSet.create(this).removeAll(other)} except it * returns an {@code ImmutableRangeSet}. * * @since 21.0 */ public ImmutableRangeSet difference(RangeSet other) { RangeSet copy = TreeRangeSet.create(this); copy.removeAll(other); return copyOf(copy); } /** * Returns a list containing the nonempty intersections of {@code range} with the ranges in this * range set. */ private ImmutableList> intersectRanges(final Range range) { if (ranges.isEmpty() || range.isEmpty()) { return ImmutableList.of(); } else if (range.encloses(span())) { return ranges; } final int fromIndex; if (range.hasLowerBound()) { fromIndex = SortedLists.binarySearch( ranges, Range.upperBoundFn(), range.lowerBound, KeyPresentBehavior.FIRST_AFTER, KeyAbsentBehavior.NEXT_HIGHER); } else { fromIndex = 0; } int toIndex; if (range.hasUpperBound()) { toIndex = SortedLists.binarySearch( ranges, Range.lowerBoundFn(), range.upperBound, KeyPresentBehavior.FIRST_PRESENT, KeyAbsentBehavior.NEXT_HIGHER); } else { toIndex = ranges.size(); } final int length = toIndex - fromIndex; if (length == 0) { return ImmutableList.of(); } else { return new ImmutableList>() { @Override public int size() { return length; } @Override public Range get(int index) { checkElementIndex(index, length); if (index == 0 || index == length - 1) { return ranges.get(index + fromIndex).intersection(range); } else { return ranges.get(index + fromIndex); } } @Override boolean isPartialView() { return true; } }; } } /** Returns a view of the intersection of this range set with the given range. */ @Override public ImmutableRangeSet subRangeSet(Range range) { if (!isEmpty()) { Range span = span(); if (range.encloses(span)) { return this; } else if (range.isConnected(span)) { return new ImmutableRangeSet(intersectRanges(range)); } } return of(); } /** * Returns an {@link ImmutableSortedSet} containing the same values in the given domain * {@linkplain RangeSet#contains contained} by this range set. * *

Note: {@code a.asSet(d).equals(b.asSet(d))} does not imply {@code a.equals(b)}! For * example, {@code a} and {@code b} could be {@code [2..4]} and {@code (1..5)}, or the empty * ranges {@code [3..3)} and {@code [4..4)}. * *

Warning: Be extremely careful what you do with the {@code asSet} view of a large * range set (such as {@code ImmutableRangeSet.of(Range.greaterThan(0))}). Certain operations on * such a set can be performed efficiently, but others (such as {@link Set#hashCode} or {@link * Collections#frequency}) can cause major performance problems. * *

The returned set's {@link Object#toString} method returns a shorthand form of the set's * contents, such as {@code "[1..100]}"}. * * @throws IllegalArgumentException if neither this range nor the domain has a lower bound, or if * neither has an upper bound */ public ImmutableSortedSet asSet(DiscreteDomain domain) { checkNotNull(domain); if (isEmpty()) { return ImmutableSortedSet.of(); } Range span = span().canonical(domain); if (!span.hasLowerBound()) { // according to the spec of canonical, neither this ImmutableRangeSet nor // the range have a lower bound throw new IllegalArgumentException( "Neither the DiscreteDomain nor this range set are bounded below"); } else if (!span.hasUpperBound()) { try { domain.maxValue(); } catch (NoSuchElementException e) { throw new IllegalArgumentException( "Neither the DiscreteDomain nor this range set are bounded above"); } } return new AsSet(domain); } private final class AsSet extends ImmutableSortedSet { private final DiscreteDomain domain; AsSet(DiscreteDomain domain) { super(Ordering.natural()); this.domain = domain; } @LazyInit @CheckForNull private transient Integer size; @Override public int size() { // racy single-check idiom Integer result = size; if (result == null) { long total = 0; for (Range range : ranges) { total += ContiguousSet.create(range, domain).size(); if (total >= Integer.MAX_VALUE) { break; } } result = size = Ints.saturatedCast(total); } return result.intValue(); } @Override public UnmodifiableIterator iterator() { return new AbstractIterator() { final Iterator> rangeItr = ranges.iterator(); Iterator elemItr = Iterators.emptyIterator(); @Override @CheckForNull protected C computeNext() { while (!elemItr.hasNext()) { if (rangeItr.hasNext()) { elemItr = ContiguousSet.create(rangeItr.next(), domain).iterator(); } else { return endOfData(); } } return elemItr.next(); } }; } @Override @GwtIncompatible("NavigableSet") public UnmodifiableIterator descendingIterator() { return new AbstractIterator() { final Iterator> rangeItr = ranges.reverse().iterator(); Iterator elemItr = Iterators.emptyIterator(); @Override @CheckForNull protected C computeNext() { while (!elemItr.hasNext()) { if (rangeItr.hasNext()) { elemItr = ContiguousSet.create(rangeItr.next(), domain).descendingIterator(); } else { return endOfData(); } } return elemItr.next(); } }; } ImmutableSortedSet subSet(Range range) { return subRangeSet(range).asSet(domain); } @Override ImmutableSortedSet headSetImpl(C toElement, boolean inclusive) { return subSet(Range.upTo(toElement, BoundType.forBoolean(inclusive))); } @Override ImmutableSortedSet subSetImpl( C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { if (!fromInclusive && !toInclusive && Range.compareOrThrow(fromElement, toElement) == 0) { return ImmutableSortedSet.of(); } return subSet( Range.range( fromElement, BoundType.forBoolean(fromInclusive), toElement, BoundType.forBoolean(toInclusive))); } @Override ImmutableSortedSet tailSetImpl(C fromElement, boolean inclusive) { return subSet(Range.downTo(fromElement, BoundType.forBoolean(inclusive))); } @Override public boolean contains(@CheckForNull Object o) { if (o == null) { return false; } try { @SuppressWarnings("unchecked") // we catch CCE's C c = (C) o; return ImmutableRangeSet.this.contains(c); } catch (ClassCastException e) { return false; } } @Override int indexOf(@CheckForNull Object target) { if (contains(target)) { @SuppressWarnings("unchecked") // if it's contained, it's definitely a C C c = (C) requireNonNull(target); long total = 0; for (Range range : ranges) { if (range.contains(c)) { return Ints.saturatedCast(total + ContiguousSet.create(range, domain).indexOf(c)); } else { total += ContiguousSet.create(range, domain).size(); } } throw new AssertionError("impossible"); } return -1; } @Override ImmutableSortedSet createDescendingSet() { return new DescendingImmutableSortedSet(this); } @Override boolean isPartialView() { return ranges.isPartialView(); } @Override public String toString() { return ranges.toString(); } @Override @J2ktIncompatible // serialization Object writeReplace() { return new AsSetSerializedForm(ranges, domain); } @J2ktIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } } private static class AsSetSerializedForm implements Serializable { private final ImmutableList> ranges; private final DiscreteDomain domain; AsSetSerializedForm(ImmutableList> ranges, DiscreteDomain domain) { this.ranges = ranges; this.domain = domain; } Object readResolve() { return new ImmutableRangeSet(ranges).asSet(domain); } } /** * Returns {@code true} if this immutable range set's implementation contains references to * user-created objects that aren't accessible via this range set's methods. This is generally * used to determine whether {@code copyOf} implementations should make an explicit copy to avoid * memory leaks. */ boolean isPartialView() { return ranges.isPartialView(); } /** Returns a new builder for an immutable range set. */ public static > Builder builder() { return new Builder(); } /** * A builder for immutable range sets. * * @since 14.0 */ public static class Builder> { private final List> ranges; public Builder() { this.ranges = Lists.newArrayList(); } // TODO(lowasser): consider adding union, in addition to add, that does allow overlap /** * Add the specified range to this builder. Adjacent ranges are permitted and will be merged, * but overlapping ranges will cause an exception when {@link #build()} is called. * * @throws IllegalArgumentException if {@code range} is empty */ @CanIgnoreReturnValue public Builder add(Range range) { checkArgument(!range.isEmpty(), "range must not be empty, but was %s", range); ranges.add(range); return this; } /** * Add all ranges from the specified range set to this builder. Adjacent ranges are permitted * and will be merged, but overlapping ranges will cause an exception when {@link #build()} is * called. */ @CanIgnoreReturnValue public Builder addAll(RangeSet ranges) { return addAll(ranges.asRanges()); } /** * Add all of the specified ranges to this builder. Adjacent ranges are permitted and will be * merged, but overlapping ranges will cause an exception when {@link #build()} is called. * * @throws IllegalArgumentException if any inserted ranges are empty * @since 21.0 */ @CanIgnoreReturnValue public Builder addAll(Iterable> ranges) { for (Range range : ranges) { add(range); } return this; } @CanIgnoreReturnValue Builder combine(Builder builder) { addAll(builder.ranges); return this; } /** * Returns an {@code ImmutableRangeSet} containing the ranges added to this builder. * * @throws IllegalArgumentException if any input ranges have nonempty overlap */ public ImmutableRangeSet build() { ImmutableList.Builder> mergedRangesBuilder = new ImmutableList.Builder<>(ranges.size()); Collections.sort(ranges, Range.rangeLexOrdering()); PeekingIterator> peekingItr = Iterators.peekingIterator(ranges.iterator()); while (peekingItr.hasNext()) { Range range = peekingItr.next(); while (peekingItr.hasNext()) { Range nextRange = peekingItr.peek(); if (range.isConnected(nextRange)) { checkArgument( range.intersection(nextRange).isEmpty(), "Overlapping ranges not permitted but found %s overlapping %s", range, nextRange); range = range.span(peekingItr.next()); } else { break; } } mergedRangesBuilder.add(range); } ImmutableList> mergedRanges = mergedRangesBuilder.build(); if (mergedRanges.isEmpty()) { return of(); } else if (mergedRanges.size() == 1 && Iterables.getOnlyElement(mergedRanges).equals(Range.all())) { return all(); } else { return new ImmutableRangeSet(mergedRanges); } } } private static final class SerializedForm implements Serializable { private final ImmutableList> ranges; SerializedForm(ImmutableList> ranges) { this.ranges = ranges; } Object readResolve() { if (ranges.isEmpty()) { return of(); } else if (ranges.equals(ImmutableList.of(Range.all()))) { return all(); } else { return new ImmutableRangeSet(ranges); } } } @J2ktIncompatible // java.io.ObjectInputStream Object writeReplace() { return new SerializedForm(ranges); } @J2ktIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws InvalidObjectException { throw new InvalidObjectException("Use SerializedForm"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy