001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014package com.google.common.collect;
015
016import static com.google.common.base.Preconditions.checkArgument;
017import static com.google.common.base.Preconditions.checkElementIndex;
018import static com.google.common.base.Preconditions.checkNotNull;
019import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_LOWER;
020import static com.google.common.collect.SortedLists.KeyPresentBehavior.ANY_PRESENT;
021
022import com.google.common.annotations.Beta;
023import com.google.common.annotations.GwtIncompatible;
024import com.google.common.collect.SortedLists.KeyAbsentBehavior;
025import com.google.common.collect.SortedLists.KeyPresentBehavior;
026import com.google.common.primitives.Ints;
027
028import java.io.Serializable;
029import java.util.Collections;
030import java.util.Iterator;
031import java.util.NoSuchElementException;
032import java.util.Set;
033
034import javax.annotation.Nullable;
035
036/**
037 * An efficient immutable implementation of a {@link RangeSet}.
038 *
039 * @author Louis Wasserman
040 * @since 14.0
041 */
042@Beta
043public final class ImmutableRangeSet<C extends Comparable> extends AbstractRangeSet<C>
044    implements Serializable {
045
046  @SuppressWarnings("unchecked")
047  private static final ImmutableRangeSet EMPTY = new ImmutableRangeSet(ImmutableList.of());
048
049  @SuppressWarnings("unchecked")
050  private static final ImmutableRangeSet ALL = new ImmutableRangeSet(ImmutableList.of(Range.all()));
051
052  /**
053   * Returns an empty immutable range set.
054   */
055  @SuppressWarnings("unchecked")
056  public static <C extends Comparable> ImmutableRangeSet<C> of() {
057    return EMPTY;
058  }
059
060  /**
061   * Returns an immutable range set containing the single range {@link Range#all()}.
062   */
063  @SuppressWarnings("unchecked")
064  static <C extends Comparable> ImmutableRangeSet<C> all() {
065    return ALL;
066  }
067
068  /**
069   * Returns an immutable range set containing the specified single range. If {@link Range#isEmpty()
070   * range.isEmpty()}, this is equivalent to {@link ImmutableRangeSet#of()}.
071   */
072  public static <C extends Comparable> ImmutableRangeSet<C> of(Range<C> range) {
073    checkNotNull(range);
074    if (range.isEmpty()) {
075      return of();
076    } else if (range.equals(Range.all())) {
077      return all();
078    } else {
079      return new ImmutableRangeSet<C>(ImmutableList.of(range));
080    }
081  }
082
083  /**
084   * Returns an immutable copy of the specified {@code RangeSet}.
085   */
086  public static <C extends Comparable> ImmutableRangeSet<C> copyOf(RangeSet<C> rangeSet) {
087    checkNotNull(rangeSet);
088    if (rangeSet.isEmpty()) {
089      return of();
090    } else if (rangeSet.encloses(Range.<C>all())) {
091      return all();
092    }
093
094    if (rangeSet instanceof ImmutableRangeSet) {
095      ImmutableRangeSet<C> immutableRangeSet = (ImmutableRangeSet<C>) rangeSet;
096      if (!immutableRangeSet.isPartialView()) {
097        return immutableRangeSet;
098      }
099    }
100    return new ImmutableRangeSet<C>(ImmutableList.copyOf(rangeSet.asRanges()));
101  }
102
103  ImmutableRangeSet(ImmutableList<Range<C>> ranges) {
104    this.ranges = ranges;
105  }
106
107  private ImmutableRangeSet(ImmutableList<Range<C>> ranges, ImmutableRangeSet<C> complement) {
108    this.ranges = ranges;
109    this.complement = complement;
110  }
111
112  private transient final ImmutableList<Range<C>> ranges;
113
114  @Override
115  public boolean encloses(Range<C> otherRange) {
116    int index = SortedLists.binarySearch(ranges,
117        Range.<C>lowerBoundFn(),
118        otherRange.lowerBound,
119        Ordering.natural(),
120        ANY_PRESENT,
121        NEXT_LOWER);
122    return index != -1 && ranges.get(index).encloses(otherRange);
123  }
124
125  @Override
126  public Range<C> rangeContaining(C value) {
127    int index = SortedLists.binarySearch(ranges,
128        Range.<C>lowerBoundFn(),
129        Cut.belowValue(value),
130        Ordering.natural(),
131        ANY_PRESENT,
132        NEXT_LOWER);
133    if (index != -1) {
134      Range<C> range = ranges.get(index);
135      return range.contains(value) ? range : null;
136    }
137    return null;
138  }
139
140  @Override
141  public Range<C> span() {
142    if (ranges.isEmpty()) {
143      throw new NoSuchElementException();
144    }
145    return Range.create(
146        ranges.get(0).lowerBound,
147        ranges.get(ranges.size() - 1).upperBound);
148  }
149
150  @Override
151  public boolean isEmpty() {
152    return ranges.isEmpty();
153  }
154
155  @Override
156  public void add(Range<C> range) {
157    throw new UnsupportedOperationException();
158  }
159
160  @Override
161  public void addAll(RangeSet<C> other) {
162    throw new UnsupportedOperationException();
163  }
164
165  @Override
166  public void remove(Range<C> range) {
167    throw new UnsupportedOperationException();
168  }
169
170  @Override
171  public void removeAll(RangeSet<C> other) {
172    throw new UnsupportedOperationException();
173  }
174
175  @Override
176  public ImmutableSet<Range<C>> asRanges() {
177    if (ranges.isEmpty()) {
178      return ImmutableSet.of();
179    }
180    return new RegularImmutableSortedSet<Range<C>>(ranges, Range.RANGE_LEX_ORDERING);
181  }
182
183  private transient ImmutableRangeSet<C> complement;
184
185  private final class ComplementRanges extends ImmutableList<Range<C>> {
186    // True if the "positive" range set is empty or bounded below.
187    private final boolean positiveBoundedBelow;
188
189    // True if the "positive" range set is empty or bounded above.
190    private final boolean positiveBoundedAbove;
191
192    private final int size;
193
194    ComplementRanges() {
195      this.positiveBoundedBelow = ranges.get(0).hasLowerBound();
196      this.positiveBoundedAbove = Iterables.getLast(ranges).hasUpperBound();
197
198      int size = ranges.size() - 1;
199      if (positiveBoundedBelow) {
200        size++;
201      }
202      if (positiveBoundedAbove) {
203        size++;
204      }
205      this.size = size;
206    }
207
208    @Override
209    public int size() {
210      return size;
211    }
212
213    @Override
214    public Range<C> get(int index) {
215      checkElementIndex(index, size);
216
217      Cut<C> lowerBound;
218      if (positiveBoundedBelow) {
219        lowerBound = (index == 0) ? Cut.<C>belowAll() : ranges.get(index - 1).upperBound;
220      } else {
221        lowerBound = ranges.get(index).upperBound;
222      }
223
224      Cut<C> upperBound;
225      if (positiveBoundedAbove && index == size - 1) {
226        upperBound = Cut.<C>aboveAll();
227      } else {
228        upperBound = ranges.get(index + (positiveBoundedBelow ? 0 : 1)).lowerBound;
229      }
230
231      return Range.create(lowerBound, upperBound);
232    }
233
234    @Override
235    boolean isPartialView() {
236      return true;
237    }
238  }
239
240  @Override
241  public ImmutableRangeSet<C> complement() {
242    ImmutableRangeSet<C> result = complement;
243    if (result != null) {
244      return result;
245    } else if (ranges.isEmpty()) {
246      return complement = all();
247    } else if (ranges.size() == 1 && ranges.get(0).equals(Range.all())) {
248      return complement = of();
249    } else {
250      ImmutableList<Range<C>> complementRanges = new ComplementRanges();
251      result = complement = new ImmutableRangeSet<C>(complementRanges, this);
252    }
253    return result;
254  }
255
256  /**
257   * Returns a list containing the nonempty intersections of {@code range}
258   * with the ranges in this range set.
259   */
260  private ImmutableList<Range<C>> intersectRanges(final Range<C> range) {
261    if (ranges.isEmpty() || range.isEmpty()) {
262      return ImmutableList.of();
263    } else if (range.encloses(span())) {
264      return ranges;
265    }
266
267    final int fromIndex;
268    if (range.hasLowerBound()) {
269      fromIndex = SortedLists.binarySearch(
270          ranges, Range.<C>upperBoundFn(), range.lowerBound, KeyPresentBehavior.FIRST_AFTER,
271          KeyAbsentBehavior.NEXT_HIGHER);
272    } else {
273      fromIndex = 0;
274    }
275
276    int toIndex;
277    if (range.hasUpperBound()) {
278      toIndex = SortedLists.binarySearch(
279          ranges, Range.<C>lowerBoundFn(), range.upperBound, KeyPresentBehavior.FIRST_PRESENT,
280          KeyAbsentBehavior.NEXT_HIGHER);
281    } else {
282      toIndex = ranges.size();
283    }
284    final int length = toIndex - fromIndex;
285    if (length == 0) {
286      return ImmutableList.of();
287    } else {
288      return new ImmutableList<Range<C>>() {
289        @Override
290        public int size() {
291          return length;
292        }
293
294        @Override
295        public Range<C> get(int index) {
296          checkElementIndex(index, length);
297          if (index == 0 || index == length - 1) {
298            return ranges.get(index + fromIndex).intersection(range);
299          } else {
300            return ranges.get(index + fromIndex);
301          }
302        }
303
304        @Override
305        boolean isPartialView() {
306          return true;
307        }
308      };
309    }
310  }
311  
312  /**
313   * Returns a view of the intersection of this range set with the given range.
314   */
315  @Override
316  public ImmutableRangeSet<C> subRangeSet(Range<C> range) {
317    if (!isEmpty()) {
318      Range<C> span = span();
319      if (range.encloses(span)) {
320        return this;
321      } else if (range.isConnected(span)) {
322        return new ImmutableRangeSet<C>(intersectRanges(range));
323      }
324    }
325    return of();
326  }
327
328  /**
329   * Returns an {@link ImmutableSortedSet} containing the same values in the given domain
330   * {@linkplain RangeSet#contains contained} by this range set.
331   *
332   * <p><b>Note:</b> {@code a.asSet(d).equals(b.asSet(d))} does not imply {@code a.equals(b)}! For
333   * example, {@code a} and {@code b} could be {@code [2..4]} and {@code (1..5)}, or the empty
334   * ranges {@code [3..3)} and {@code [4..4)}.
335   *
336   * <p><b>Warning:</b> Be extremely careful what you do with the {@code asSet} view of a large
337   * range set (such as {@code ImmutableRangeSet.of(Range.greaterThan(0))}). Certain operations on
338   * such a set can be performed efficiently, but others (such as {@link Set#hashCode} or
339   * {@link Collections#frequency}) can cause major performance problems.
340   *
341   * <p>The returned set's {@link Object#toString} method returns a short-hand form of the set's
342   * contents, such as {@code "[1..100]}"}.
343   *
344   * @throws IllegalArgumentException if neither this range nor the domain has a lower bound, or if
345   *         neither has an upper bound
346   */
347  public ImmutableSortedSet<C> asSet(DiscreteDomain<C> domain) {
348    checkNotNull(domain);
349    if (isEmpty()) {
350      return ImmutableSortedSet.of();
351    }
352    Range<C> span = span().canonical(domain);
353    if (!span.hasLowerBound()) {
354      // according to the spec of canonical, neither this ImmutableRangeSet nor
355      // the range have a lower bound
356      throw new IllegalArgumentException(
357          "Neither the DiscreteDomain nor this range set are bounded below");
358    } else if (!span.hasUpperBound()) {
359      try {
360        domain.maxValue();
361      } catch (NoSuchElementException e) {
362        throw new IllegalArgumentException(
363            "Neither the DiscreteDomain nor this range set are bounded above");
364      }
365    }
366
367    return new AsSet(domain);
368  }
369
370  private final class AsSet extends ImmutableSortedSet<C> {
371    private final DiscreteDomain<C> domain;
372
373    AsSet(DiscreteDomain<C> domain) {
374      super(Ordering.natural());
375      this.domain = domain;
376    }
377
378    private transient Integer size;
379
380    @Override
381    public int size() {
382      // racy single-check idiom
383      Integer result = size;
384      if (result == null) {
385        long total = 0;
386        for (Range<C> range : ranges) {
387          total += range.asSet(domain).size();
388          if (total >= Integer.MAX_VALUE) {
389            break;
390          }
391        }
392        result = size = Ints.saturatedCast(total);
393      }
394      return result.intValue();
395    }
396
397    @Override
398    public UnmodifiableIterator<C> iterator() {
399      return new AbstractIterator<C>() {
400        final Iterator<Range<C>> rangeItr = ranges.iterator();
401        Iterator<C> elemItr = Iterators.emptyIterator();
402
403        @Override
404        protected C computeNext() {
405          while (!elemItr.hasNext()) {
406            if (rangeItr.hasNext()) {
407              elemItr = rangeItr.next().asSet(domain).iterator();
408            } else {
409              return endOfData();
410            }
411          }
412          return elemItr.next();
413        }
414      };
415    }
416
417    @Override
418    @GwtIncompatible("NavigableSet")
419    public UnmodifiableIterator<C> descendingIterator() {
420      return new AbstractIterator<C>() {
421        final Iterator<Range<C>> rangeItr = ranges.reverse().iterator();
422        Iterator<C> elemItr = Iterators.emptyIterator();
423
424        @Override
425        protected C computeNext() {
426          while (!elemItr.hasNext()) {
427            if (rangeItr.hasNext()) {
428              elemItr = rangeItr.next().asSet(domain).descendingIterator();
429            } else {
430              return endOfData();
431            }
432          }
433          return elemItr.next();
434        }
435      };
436    }
437
438    ImmutableSortedSet<C> subSet(Range<C> range) {
439      return subRangeSet(range).asSet(domain);
440    }
441
442    @Override
443    ImmutableSortedSet<C> headSetImpl(C toElement, boolean inclusive) {
444      return subSet(Range.upTo(toElement, BoundType.forBoolean(inclusive)));
445    }
446
447    @Override
448    ImmutableSortedSet<C> subSetImpl(
449        C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) {
450      if (!fromInclusive && !toInclusive && Range.compareOrThrow(fromElement, toElement) == 0) {
451        return ImmutableSortedSet.of();
452      }
453      return subSet(Range.range(
454          fromElement, BoundType.forBoolean(fromInclusive),
455          toElement, BoundType.forBoolean(toInclusive)));
456    }
457
458    @Override
459    ImmutableSortedSet<C> tailSetImpl(C fromElement, boolean inclusive) {
460      return subSet(Range.downTo(fromElement, BoundType.forBoolean(inclusive)));
461    }
462
463    @Override
464    public boolean contains(@Nullable Object o) {
465      if (o == null) {
466        return false;
467      }
468      try {
469        @SuppressWarnings("unchecked") // we catch CCE's
470        C c = (C) o;
471        return ImmutableRangeSet.this.contains(c);
472      } catch (ClassCastException e) {
473        return false;
474      }
475    }
476
477    @Override
478    int indexOf(Object target) {
479      if (contains(target)) {
480        @SuppressWarnings("unchecked") // if it's contained, it's definitely a C
481        C c = (C) target;
482        long total = 0;
483        for (Range<C> range : ranges) {
484          if (range.contains(c)) {
485            return Ints.saturatedCast(total + range.asSet(domain).indexOf(c));
486          } else {
487            total += range.asSet(domain).size();
488          }
489        }
490        throw new AssertionError("impossible");
491      }
492      return -1;
493    }
494
495    @Override
496    boolean isPartialView() {
497      return ranges.isPartialView();
498    }
499
500    @Override
501    public String toString() {
502      return ranges.toString();
503    }
504
505    @Override
506    Object writeReplace() {
507      return new AsSetSerializedForm<C>(ranges, domain);
508    }
509  }
510
511  private static class AsSetSerializedForm<C extends Comparable> implements Serializable {
512    private final ImmutableList<Range<C>> ranges;
513    private final DiscreteDomain<C> domain;
514
515    AsSetSerializedForm(ImmutableList<Range<C>> ranges, DiscreteDomain<C> domain) {
516      this.ranges = ranges;
517      this.domain = domain;
518    }
519
520    Object readResolve() {
521      return new ImmutableRangeSet<C>(ranges).asSet(domain);
522    }
523  }
524
525  boolean isPartialView() {
526    return ranges.isPartialView();
527  }
528
529  /**
530   * Returns a new builder for an immutable range set.
531   */
532  public static <C extends Comparable<?>> Builder<C> builder() {
533    return new Builder<C>();
534  }
535
536  /**
537   * A builder for immutable range sets.
538   */
539  public static class Builder<C extends Comparable<?>> {
540    private final RangeSet<C> rangeSet;
541
542    public Builder() {
543      this.rangeSet = TreeRangeSet.create();
544    }
545
546    /**
547     * Add the specified range to this builder.  Adjacent/abutting ranges are permitted, but
548     * empty ranges, or ranges with nonempty overlap, are forbidden.
549     *
550     * @throws IllegalArgumentException if {@code range} is empty or has nonempty intersection with
551     *         any ranges already added to the builder
552     */
553    public Builder<C> add(Range<C> range) {
554      if (range.isEmpty()) {
555        throw new IllegalArgumentException("range must not be empty, but was " + range);
556      } else if (!rangeSet.complement().encloses(range)) {
557        for (Range<C> currentRange : rangeSet.asRanges()) {
558          checkArgument(
559              !currentRange.isConnected(range) || currentRange.intersection(range).isEmpty(),
560              "Ranges may not overlap, but received %s and %s", currentRange, range);
561        }
562        throw new AssertionError("should have thrown an IAE above");
563      }
564      rangeSet.add(range);
565      return this;
566    }
567
568    /**
569     * Add all ranges from the specified range set to this builder. Duplicate or connected ranges
570     * are permitted, and will be merged in the resulting immutable range set.
571     */
572    public Builder<C> addAll(RangeSet<C> ranges) {
573      for (Range<C> range : ranges.asRanges()) {
574        add(range);
575      }
576      return this;
577    }
578
579    /**
580     * Returns an {@code ImmutableRangeSet} containing the ranges added to this builder.
581     */
582    public ImmutableRangeSet<C> build() {
583      return copyOf(rangeSet);
584    }
585  }
586
587  private static final class SerializedForm<C extends Comparable> implements Serializable {
588    private final ImmutableList<Range<C>> ranges;
589
590    SerializedForm(ImmutableList<Range<C>> ranges) {
591      this.ranges = ranges;
592    }
593
594    Object readResolve() {
595      if (ranges.isEmpty()) {
596        return of();
597      } else if (ranges.equals(ImmutableList.of(Range.all()))) {
598        return all();
599      } else {
600        return new ImmutableRangeSet<C>(ranges);
601      }
602    }
603  }
604
605  Object writeReplace() {
606    return new SerializedForm<C>(ranges);
607  }
608}