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