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 */ 014 015package com.google.common.collect; 016 017import static com.google.common.base.Preconditions.checkArgument; 018import static com.google.common.base.Preconditions.checkElementIndex; 019import static com.google.common.base.Preconditions.checkNotNull; 020import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_HIGHER; 021import static com.google.common.collect.SortedLists.KeyAbsentBehavior.NEXT_LOWER; 022import static com.google.common.collect.SortedLists.KeyPresentBehavior.ANY_PRESENT; 023 024import com.google.common.annotations.Beta; 025import com.google.common.annotations.GwtIncompatible; 026import com.google.common.collect.SortedLists.KeyAbsentBehavior; 027import com.google.common.collect.SortedLists.KeyPresentBehavior; 028import com.google.common.primitives.Ints; 029import com.google.errorprone.annotations.CanIgnoreReturnValue; 030import com.google.errorprone.annotations.concurrent.LazyInit; 031import java.io.Serializable; 032import java.util.Collections; 033import java.util.Iterator; 034import java.util.List; 035import java.util.NoSuchElementException; 036import java.util.Set; 037import java.util.stream.Collector; 038import org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl; 039import org.checkerframework.checker.nullness.compatqual.NullableDecl; 040 041/** 042 * A {@link RangeSet} whose contents will never change, with many other important properties 043 * detailed at {@link ImmutableCollection}. 044 * 045 * @author Louis Wasserman 046 * @since 14.0 047 */ 048@Beta 049@GwtIncompatible 050public final class ImmutableRangeSet<C extends Comparable> extends AbstractRangeSet<C> 051 implements Serializable { 052 053 private static final ImmutableRangeSet<Comparable<?>> EMPTY = 054 new ImmutableRangeSet<>(ImmutableList.<Range<Comparable<?>>>of()); 055 056 private static final ImmutableRangeSet<Comparable<?>> ALL = 057 new ImmutableRangeSet<>(ImmutableList.of(Range.<Comparable<?>>all())); 058 059 /** 060 * Returns a {@code Collector} that accumulates the input elements into a new {@code 061 * ImmutableRangeSet}. As in {@link Builder}, overlapping ranges are not permitted and adjacent 062 * ranges will be merged. 063 * 064 * @since 23.1 065 */ 066 @Beta 067 public static <E extends Comparable<? super E>> 068 Collector<Range<E>, ?, ImmutableRangeSet<E>> toImmutableRangeSet() { 069 return CollectCollectors.toImmutableRangeSet(); 070 } 071 072 /** Returns an empty immutable range set. */ 073 @SuppressWarnings("unchecked") 074 public static <C extends Comparable> ImmutableRangeSet<C> of() { 075 return (ImmutableRangeSet<C>) EMPTY; 076 } 077 078 /** Returns an immutable range set containing the single range {@link Range#all()}. */ 079 @SuppressWarnings("unchecked") 080 static <C extends Comparable> ImmutableRangeSet<C> all() { 081 return (ImmutableRangeSet<C>) ALL; 082 } 083 084 /** 085 * Returns an immutable range set containing the specified single range. If {@link Range#isEmpty() 086 * range.isEmpty()}, this is equivalent to {@link ImmutableRangeSet#of()}. 087 */ 088 public static <C extends Comparable> ImmutableRangeSet<C> of(Range<C> range) { 089 checkNotNull(range); 090 if (range.isEmpty()) { 091 return of(); 092 } else if (range.equals(Range.all())) { 093 return all(); 094 } else { 095 return new ImmutableRangeSet<C>(ImmutableList.of(range)); 096 } 097 } 098 099 /** Returns an immutable copy of the specified {@code RangeSet}. */ 100 public static <C extends Comparable> ImmutableRangeSet<C> copyOf(RangeSet<C> rangeSet) { 101 checkNotNull(rangeSet); 102 if (rangeSet.isEmpty()) { 103 return of(); 104 } else if (rangeSet.encloses(Range.<C>all())) { 105 return all(); 106 } 107 108 if (rangeSet instanceof ImmutableRangeSet) { 109 ImmutableRangeSet<C> immutableRangeSet = (ImmutableRangeSet<C>) rangeSet; 110 if (!immutableRangeSet.isPartialView()) { 111 return immutableRangeSet; 112 } 113 } 114 return new ImmutableRangeSet<C>(ImmutableList.copyOf(rangeSet.asRanges())); 115 } 116 117 /** 118 * Returns an {@code ImmutableRangeSet} representing the union of the specified ranges. 119 * 120 * <p>This is the smallest {@code RangeSet} which encloses each of the specified ranges. Duplicate 121 * or connected ranges are permitted, and will be coalesced in the result. 122 * 123 * @since 21.0 124 */ 125 public static <C extends Comparable<?>> ImmutableRangeSet<C> unionOf(Iterable<Range<C>> ranges) { 126 return copyOf(TreeRangeSet.create(ranges)); 127 } 128 129 /** 130 * Returns an {@code ImmutableRangeSet} containing each of the specified disjoint ranges. 131 * Overlapping ranges and empty ranges are forbidden, though adjacent ranges are permitted and 132 * will be merged. 133 * 134 * @throws IllegalArgumentException if any ranges overlap or are empty 135 * @since 21.0 136 */ 137 public static <C extends Comparable<?>> ImmutableRangeSet<C> copyOf(Iterable<Range<C>> ranges) { 138 return new ImmutableRangeSet.Builder<C>().addAll(ranges).build(); 139 } 140 141 ImmutableRangeSet(ImmutableList<Range<C>> ranges) { 142 this.ranges = ranges; 143 } 144 145 private ImmutableRangeSet(ImmutableList<Range<C>> ranges, ImmutableRangeSet<C> complement) { 146 this.ranges = ranges; 147 this.complement = complement; 148 } 149 150 private final transient ImmutableList<Range<C>> ranges; 151 152 @Override 153 public boolean intersects(Range<C> otherRange) { 154 int ceilingIndex = 155 SortedLists.binarySearch( 156 ranges, 157 Range.<C>lowerBoundFn(), 158 otherRange.lowerBound, 159 Ordering.natural(), 160 ANY_PRESENT, 161 NEXT_HIGHER); 162 if (ceilingIndex < ranges.size() 163 && ranges.get(ceilingIndex).isConnected(otherRange) 164 && !ranges.get(ceilingIndex).intersection(otherRange).isEmpty()) { 165 return true; 166 } 167 return ceilingIndex > 0 168 && ranges.get(ceilingIndex - 1).isConnected(otherRange) 169 && !ranges.get(ceilingIndex - 1).intersection(otherRange).isEmpty(); 170 } 171 172 @Override 173 public boolean encloses(Range<C> otherRange) { 174 int index = 175 SortedLists.binarySearch( 176 ranges, 177 Range.<C>lowerBoundFn(), 178 otherRange.lowerBound, 179 Ordering.natural(), 180 ANY_PRESENT, 181 NEXT_LOWER); 182 return index != -1 && ranges.get(index).encloses(otherRange); 183 } 184 185 @Override 186 public Range<C> rangeContaining(C value) { 187 int index = 188 SortedLists.binarySearch( 189 ranges, 190 Range.<C>lowerBoundFn(), 191 Cut.belowValue(value), 192 Ordering.natural(), 193 ANY_PRESENT, 194 NEXT_LOWER); 195 if (index != -1) { 196 Range<C> range = ranges.get(index); 197 return range.contains(value) ? range : null; 198 } 199 return null; 200 } 201 202 @Override 203 public Range<C> span() { 204 if (ranges.isEmpty()) { 205 throw new NoSuchElementException(); 206 } 207 return Range.create(ranges.get(0).lowerBound, ranges.get(ranges.size() - 1).upperBound); 208 } 209 210 @Override 211 public boolean isEmpty() { 212 return ranges.isEmpty(); 213 } 214 215 /** 216 * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. 217 * 218 * @throws UnsupportedOperationException always 219 * @deprecated Unsupported operation. 220 */ 221 @Deprecated 222 @Override 223 public void add(Range<C> range) { 224 throw new UnsupportedOperationException(); 225 } 226 227 /** 228 * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. 229 * 230 * @throws UnsupportedOperationException always 231 * @deprecated Unsupported operation. 232 */ 233 @Deprecated 234 @Override 235 public void addAll(RangeSet<C> other) { 236 throw new UnsupportedOperationException(); 237 } 238 239 /** 240 * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. 241 * 242 * @throws UnsupportedOperationException always 243 * @deprecated Unsupported operation. 244 */ 245 @Deprecated 246 @Override 247 public void addAll(Iterable<Range<C>> other) { 248 throw new UnsupportedOperationException(); 249 } 250 251 /** 252 * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. 253 * 254 * @throws UnsupportedOperationException always 255 * @deprecated Unsupported operation. 256 */ 257 @Deprecated 258 @Override 259 public void remove(Range<C> range) { 260 throw new UnsupportedOperationException(); 261 } 262 263 /** 264 * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. 265 * 266 * @throws UnsupportedOperationException always 267 * @deprecated Unsupported operation. 268 */ 269 @Deprecated 270 @Override 271 public void removeAll(RangeSet<C> other) { 272 throw new UnsupportedOperationException(); 273 } 274 275 /** 276 * Guaranteed to throw an exception and leave the {@code RangeSet} unmodified. 277 * 278 * @throws UnsupportedOperationException always 279 * @deprecated Unsupported operation. 280 */ 281 @Deprecated 282 @Override 283 public void removeAll(Iterable<Range<C>> other) { 284 throw new UnsupportedOperationException(); 285 } 286 287 @Override 288 public ImmutableSet<Range<C>> asRanges() { 289 if (ranges.isEmpty()) { 290 return ImmutableSet.of(); 291 } 292 return new RegularImmutableSortedSet<>(ranges, Range.<C>rangeLexOrdering()); 293 } 294 295 @Override 296 public ImmutableSet<Range<C>> asDescendingSetOfRanges() { 297 if (ranges.isEmpty()) { 298 return ImmutableSet.of(); 299 } 300 return new RegularImmutableSortedSet<>(ranges.reverse(), Range.<C>rangeLexOrdering().reverse()); 301 } 302 303 @LazyInit private transient ImmutableRangeSet<C> complement; 304 305 private final class ComplementRanges extends ImmutableList<Range<C>> { 306 // True if the "positive" range set is empty or bounded below. 307 private final boolean positiveBoundedBelow; 308 309 // True if the "positive" range set is empty or bounded above. 310 private final boolean positiveBoundedAbove; 311 312 private final int size; 313 314 ComplementRanges() { 315 this.positiveBoundedBelow = ranges.get(0).hasLowerBound(); 316 this.positiveBoundedAbove = Iterables.getLast(ranges).hasUpperBound(); 317 318 int size = ranges.size() - 1; 319 if (positiveBoundedBelow) { 320 size++; 321 } 322 if (positiveBoundedAbove) { 323 size++; 324 } 325 this.size = size; 326 } 327 328 @Override 329 public int size() { 330 return size; 331 } 332 333 @Override 334 public Range<C> get(int index) { 335 checkElementIndex(index, size); 336 337 Cut<C> lowerBound; 338 if (positiveBoundedBelow) { 339 lowerBound = (index == 0) ? Cut.<C>belowAll() : ranges.get(index - 1).upperBound; 340 } else { 341 lowerBound = ranges.get(index).upperBound; 342 } 343 344 Cut<C> upperBound; 345 if (positiveBoundedAbove && index == size - 1) { 346 upperBound = Cut.<C>aboveAll(); 347 } else { 348 upperBound = ranges.get(index + (positiveBoundedBelow ? 0 : 1)).lowerBound; 349 } 350 351 return Range.create(lowerBound, upperBound); 352 } 353 354 @Override 355 boolean isPartialView() { 356 return true; 357 } 358 } 359 360 @Override 361 public ImmutableRangeSet<C> complement() { 362 ImmutableRangeSet<C> result = complement; 363 if (result != null) { 364 return result; 365 } else if (ranges.isEmpty()) { 366 return complement = all(); 367 } else if (ranges.size() == 1 && ranges.get(0).equals(Range.all())) { 368 return complement = of(); 369 } else { 370 ImmutableList<Range<C>> complementRanges = new ComplementRanges(); 371 result = complement = new ImmutableRangeSet<C>(complementRanges, this); 372 } 373 return result; 374 } 375 376 /** 377 * Returns a new range set consisting of the union of this range set and {@code other}. 378 * 379 * <p>This is essentially the same as {@code TreeRangeSet.create(this).addAll(other)} except it 380 * returns an {@code ImmutableRangeSet}. 381 * 382 * @since 21.0 383 */ 384 public ImmutableRangeSet<C> union(RangeSet<C> other) { 385 return unionOf(Iterables.concat(asRanges(), other.asRanges())); 386 } 387 388 /** 389 * Returns a new range set consisting of the intersection of this range set and {@code other}. 390 * 391 * <p>This is essentially the same as {@code 392 * TreeRangeSet.create(this).removeAll(other.complement())} except it returns an {@code 393 * ImmutableRangeSet}. 394 * 395 * @since 21.0 396 */ 397 public ImmutableRangeSet<C> intersection(RangeSet<C> other) { 398 RangeSet<C> copy = TreeRangeSet.create(this); 399 copy.removeAll(other.complement()); 400 return copyOf(copy); 401 } 402 403 /** 404 * Returns a new range set consisting of the difference of this range set and {@code other}. 405 * 406 * <p>This is essentially the same as {@code TreeRangeSet.create(this).removeAll(other)} except it 407 * returns an {@code ImmutableRangeSet}. 408 * 409 * @since 21.0 410 */ 411 public ImmutableRangeSet<C> difference(RangeSet<C> other) { 412 RangeSet<C> copy = TreeRangeSet.create(this); 413 copy.removeAll(other); 414 return copyOf(copy); 415 } 416 417 /** 418 * Returns a list containing the nonempty intersections of {@code range} with the ranges in this 419 * range set. 420 */ 421 private ImmutableList<Range<C>> intersectRanges(final Range<C> range) { 422 if (ranges.isEmpty() || range.isEmpty()) { 423 return ImmutableList.of(); 424 } else if (range.encloses(span())) { 425 return ranges; 426 } 427 428 final int fromIndex; 429 if (range.hasLowerBound()) { 430 fromIndex = 431 SortedLists.binarySearch( 432 ranges, 433 Range.<C>upperBoundFn(), 434 range.lowerBound, 435 KeyPresentBehavior.FIRST_AFTER, 436 KeyAbsentBehavior.NEXT_HIGHER); 437 } else { 438 fromIndex = 0; 439 } 440 441 int toIndex; 442 if (range.hasUpperBound()) { 443 toIndex = 444 SortedLists.binarySearch( 445 ranges, 446 Range.<C>lowerBoundFn(), 447 range.upperBound, 448 KeyPresentBehavior.FIRST_PRESENT, 449 KeyAbsentBehavior.NEXT_HIGHER); 450 } else { 451 toIndex = ranges.size(); 452 } 453 final int length = toIndex - fromIndex; 454 if (length == 0) { 455 return ImmutableList.of(); 456 } else { 457 return new ImmutableList<Range<C>>() { 458 @Override 459 public int size() { 460 return length; 461 } 462 463 @Override 464 public Range<C> get(int index) { 465 checkElementIndex(index, length); 466 if (index == 0 || index == length - 1) { 467 return ranges.get(index + fromIndex).intersection(range); 468 } else { 469 return ranges.get(index + fromIndex); 470 } 471 } 472 473 @Override 474 boolean isPartialView() { 475 return true; 476 } 477 }; 478 } 479 } 480 481 /** Returns a view of the intersection of this range set with the given range. */ 482 @Override 483 public ImmutableRangeSet<C> subRangeSet(Range<C> range) { 484 if (!isEmpty()) { 485 Range<C> span = span(); 486 if (range.encloses(span)) { 487 return this; 488 } else if (range.isConnected(span)) { 489 return new ImmutableRangeSet<C>(intersectRanges(range)); 490 } 491 } 492 return of(); 493 } 494 495 /** 496 * Returns an {@link ImmutableSortedSet} containing the same values in the given domain 497 * {@linkplain RangeSet#contains contained} by this range set. 498 * 499 * <p><b>Note:</b> {@code a.asSet(d).equals(b.asSet(d))} does not imply {@code a.equals(b)}! For 500 * example, {@code a} and {@code b} could be {@code [2..4]} and {@code (1..5)}, or the empty 501 * ranges {@code [3..3)} and {@code [4..4)}. 502 * 503 * <p><b>Warning:</b> Be extremely careful what you do with the {@code asSet} view of a large 504 * range set (such as {@code ImmutableRangeSet.of(Range.greaterThan(0))}). Certain operations on 505 * such a set can be performed efficiently, but others (such as {@link Set#hashCode} or {@link 506 * Collections#frequency}) can cause major performance problems. 507 * 508 * <p>The returned set's {@link Object#toString} method returns a short-hand form of the set's 509 * contents, such as {@code "[1..100]}"}. 510 * 511 * @throws IllegalArgumentException if neither this range nor the domain has a lower bound, or if 512 * neither has an upper bound 513 */ 514 public ImmutableSortedSet<C> asSet(DiscreteDomain<C> domain) { 515 checkNotNull(domain); 516 if (isEmpty()) { 517 return ImmutableSortedSet.of(); 518 } 519 Range<C> span = span().canonical(domain); 520 if (!span.hasLowerBound()) { 521 // according to the spec of canonical, neither this ImmutableRangeSet nor 522 // the range have a lower bound 523 throw new IllegalArgumentException( 524 "Neither the DiscreteDomain nor this range set are bounded below"); 525 } else if (!span.hasUpperBound()) { 526 try { 527 domain.maxValue(); 528 } catch (NoSuchElementException e) { 529 throw new IllegalArgumentException( 530 "Neither the DiscreteDomain nor this range set are bounded above"); 531 } 532 } 533 534 return new AsSet(domain); 535 } 536 537 private final class AsSet extends ImmutableSortedSet<C> { 538 private final DiscreteDomain<C> domain; 539 540 AsSet(DiscreteDomain<C> domain) { 541 super(Ordering.natural()); 542 this.domain = domain; 543 } 544 545 @MonotonicNonNullDecl private transient Integer size; 546 547 @Override 548 public int size() { 549 // racy single-check idiom 550 Integer result = size; 551 if (result == null) { 552 long total = 0; 553 for (Range<C> range : ranges) { 554 total += ContiguousSet.create(range, domain).size(); 555 if (total >= Integer.MAX_VALUE) { 556 break; 557 } 558 } 559 result = size = Ints.saturatedCast(total); 560 } 561 return result.intValue(); 562 } 563 564 @Override 565 public UnmodifiableIterator<C> iterator() { 566 return new AbstractIterator<C>() { 567 final Iterator<Range<C>> rangeItr = ranges.iterator(); 568 Iterator<C> elemItr = Iterators.emptyIterator(); 569 570 @Override 571 protected C computeNext() { 572 while (!elemItr.hasNext()) { 573 if (rangeItr.hasNext()) { 574 elemItr = ContiguousSet.create(rangeItr.next(), domain).iterator(); 575 } else { 576 return endOfData(); 577 } 578 } 579 return elemItr.next(); 580 } 581 }; 582 } 583 584 @Override 585 @GwtIncompatible("NavigableSet") 586 public UnmodifiableIterator<C> descendingIterator() { 587 return new AbstractIterator<C>() { 588 final Iterator<Range<C>> rangeItr = ranges.reverse().iterator(); 589 Iterator<C> elemItr = Iterators.emptyIterator(); 590 591 @Override 592 protected C computeNext() { 593 while (!elemItr.hasNext()) { 594 if (rangeItr.hasNext()) { 595 elemItr = ContiguousSet.create(rangeItr.next(), domain).descendingIterator(); 596 } else { 597 return endOfData(); 598 } 599 } 600 return elemItr.next(); 601 } 602 }; 603 } 604 605 ImmutableSortedSet<C> subSet(Range<C> range) { 606 return subRangeSet(range).asSet(domain); 607 } 608 609 @Override 610 ImmutableSortedSet<C> headSetImpl(C toElement, boolean inclusive) { 611 return subSet(Range.upTo(toElement, BoundType.forBoolean(inclusive))); 612 } 613 614 @Override 615 ImmutableSortedSet<C> subSetImpl( 616 C fromElement, boolean fromInclusive, C toElement, boolean toInclusive) { 617 if (!fromInclusive && !toInclusive && Range.compareOrThrow(fromElement, toElement) == 0) { 618 return ImmutableSortedSet.of(); 619 } 620 return subSet( 621 Range.range( 622 fromElement, BoundType.forBoolean(fromInclusive), 623 toElement, BoundType.forBoolean(toInclusive))); 624 } 625 626 @Override 627 ImmutableSortedSet<C> tailSetImpl(C fromElement, boolean inclusive) { 628 return subSet(Range.downTo(fromElement, BoundType.forBoolean(inclusive))); 629 } 630 631 @Override 632 public boolean contains(@NullableDecl Object o) { 633 if (o == null) { 634 return false; 635 } 636 try { 637 @SuppressWarnings("unchecked") // we catch CCE's 638 C c = (C) o; 639 return ImmutableRangeSet.this.contains(c); 640 } catch (ClassCastException e) { 641 return false; 642 } 643 } 644 645 @Override 646 int indexOf(Object target) { 647 if (contains(target)) { 648 @SuppressWarnings("unchecked") // if it's contained, it's definitely a C 649 C c = (C) target; 650 long total = 0; 651 for (Range<C> range : ranges) { 652 if (range.contains(c)) { 653 return Ints.saturatedCast(total + ContiguousSet.create(range, domain).indexOf(c)); 654 } else { 655 total += ContiguousSet.create(range, domain).size(); 656 } 657 } 658 throw new AssertionError("impossible"); 659 } 660 return -1; 661 } 662 663 @Override 664 ImmutableSortedSet<C> createDescendingSet() { 665 return new DescendingImmutableSortedSet<C>(this); 666 } 667 668 @Override 669 boolean isPartialView() { 670 return ranges.isPartialView(); 671 } 672 673 @Override 674 public String toString() { 675 return ranges.toString(); 676 } 677 678 @Override 679 Object writeReplace() { 680 return new AsSetSerializedForm<C>(ranges, domain); 681 } 682 } 683 684 private static class AsSetSerializedForm<C extends Comparable> implements Serializable { 685 private final ImmutableList<Range<C>> ranges; 686 private final DiscreteDomain<C> domain; 687 688 AsSetSerializedForm(ImmutableList<Range<C>> ranges, DiscreteDomain<C> domain) { 689 this.ranges = ranges; 690 this.domain = domain; 691 } 692 693 Object readResolve() { 694 return new ImmutableRangeSet<C>(ranges).asSet(domain); 695 } 696 } 697 698 /** 699 * Returns {@code true} if this immutable range set's implementation contains references to 700 * user-created objects that aren't accessible via this range set's methods. This is generally 701 * used to determine whether {@code copyOf} implementations should make an explicit copy to avoid 702 * memory leaks. 703 */ 704 boolean isPartialView() { 705 return ranges.isPartialView(); 706 } 707 708 /** Returns a new builder for an immutable range set. */ 709 public static <C extends Comparable<?>> Builder<C> builder() { 710 return new Builder<C>(); 711 } 712 713 /** 714 * A builder for immutable range sets. 715 * 716 * @since 14.0 717 */ 718 public static class Builder<C extends Comparable<?>> { 719 private final List<Range<C>> ranges; 720 721 public Builder() { 722 this.ranges = Lists.newArrayList(); 723 } 724 725 // TODO(lowasser): consider adding union, in addition to add, that does allow overlap 726 727 /** 728 * Add the specified range to this builder. Adjacent ranges are permitted and will be merged, 729 * but overlapping ranges will cause an exception when {@link #build()} is called. 730 * 731 * @throws IllegalArgumentException if {@code range} is empty 732 */ 733 @CanIgnoreReturnValue 734 public Builder<C> add(Range<C> range) { 735 checkArgument(!range.isEmpty(), "range must not be empty, but was %s", range); 736 ranges.add(range); 737 return this; 738 } 739 740 /** 741 * Add all ranges from the specified range set to this builder. Adjacent ranges are permitted 742 * and will be merged, but overlapping ranges will cause an exception when {@link #build()} is 743 * called. 744 */ 745 @CanIgnoreReturnValue 746 public Builder<C> addAll(RangeSet<C> ranges) { 747 return addAll(ranges.asRanges()); 748 } 749 750 /** 751 * Add all of the specified ranges to this builder. Adjacent ranges are permitted and will be 752 * merged, but overlapping ranges will cause an exception when {@link #build()} is called. 753 * 754 * @throws IllegalArgumentException if any inserted ranges are empty 755 * @since 21.0 756 */ 757 @CanIgnoreReturnValue 758 public Builder<C> addAll(Iterable<Range<C>> ranges) { 759 for (Range<C> range : ranges) { 760 add(range); 761 } 762 return this; 763 } 764 765 @CanIgnoreReturnValue 766 Builder<C> combine(Builder<C> builder) { 767 addAll(builder.ranges); 768 return this; 769 } 770 771 /** 772 * Returns an {@code ImmutableRangeSet} containing the ranges added to this builder. 773 * 774 * @throws IllegalArgumentException if any input ranges have nonempty overlap 775 */ 776 public ImmutableRangeSet<C> build() { 777 ImmutableList.Builder<Range<C>> mergedRangesBuilder = 778 new ImmutableList.Builder<>(ranges.size()); 779 Collections.sort(ranges, Range.<C>rangeLexOrdering()); 780 PeekingIterator<Range<C>> peekingItr = Iterators.peekingIterator(ranges.iterator()); 781 while (peekingItr.hasNext()) { 782 Range<C> range = peekingItr.next(); 783 while (peekingItr.hasNext()) { 784 Range<C> nextRange = peekingItr.peek(); 785 if (range.isConnected(nextRange)) { 786 checkArgument( 787 range.intersection(nextRange).isEmpty(), 788 "Overlapping ranges not permitted but found %s overlapping %s", 789 range, 790 nextRange); 791 range = range.span(peekingItr.next()); 792 } else { 793 break; 794 } 795 } 796 mergedRangesBuilder.add(range); 797 } 798 ImmutableList<Range<C>> mergedRanges = mergedRangesBuilder.build(); 799 if (mergedRanges.isEmpty()) { 800 return of(); 801 } else if (mergedRanges.size() == 1 802 && Iterables.getOnlyElement(mergedRanges).equals(Range.all())) { 803 return all(); 804 } else { 805 return new ImmutableRangeSet<C>(mergedRanges); 806 } 807 } 808 } 809 810 private static final class SerializedForm<C extends Comparable> implements Serializable { 811 private final ImmutableList<Range<C>> ranges; 812 813 SerializedForm(ImmutableList<Range<C>> ranges) { 814 this.ranges = ranges; 815 } 816 817 Object readResolve() { 818 if (ranges.isEmpty()) { 819 return of(); 820 } else if (ranges.equals(ImmutableList.of(Range.all()))) { 821 return all(); 822 } else { 823 return new ImmutableRangeSet<C>(ranges); 824 } 825 } 826 } 827 828 Object writeReplace() { 829 return new SerializedForm<C>(ranges); 830 } 831}