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