Class IntervalSet<T extends Comparable<? super T>>

java.lang.Object
com.cedarsoftware.util.IntervalSet<T>
Type Parameters:
T - the type of interval boundaries, must implement Comparable
All Implemented Interfaces:
Iterable<IntervalSet.Interval<T>>

public class IntervalSet<T extends Comparable<? super T>> extends Object implements Iterable<IntervalSet.Interval<T>>
Thread-safe set of closed intervals [start, end] (both boundaries inclusive) for any Comparable type.

Core Capabilities

IntervalSet efficiently manages collections of intervals with the following key features:

  • O(log n) performance - Uses ConcurrentSkipListMap for efficient lookups, insertions, and range queries
  • Thread-safe - Lock-free reads with minimal locking for writes only
  • Flexible merging behavior - Configurable auto-merge vs. discrete storage modes
  • Intelligent interval splitting - Automatically splits intervals during removal operations
  • Rich query API - Comprehensive set of methods for finding, filtering, and navigating intervals
  • Type-safe boundaries - Supports precise boundary calculations for 20+ built-in types

Auto-Merge vs. Discrete Modes

The behavior of interval storage is controlled by the autoMerge flag set during construction:

Auto-Merge Mode (default: autoMerge = true)

Overlapping intervals are automatically merged into larger, non-overlapping intervals:


   IntervalSet<Integer> set = new IntervalSet<>();  // autoMerge = true by default
   set.add(1, 5);
   set.add(3, 8);    // Merges with [1,5] to create [1,8]
   set.add(10, 15);  // Separate interval since no overlap
   // Result: [1,8], [10,15]
 

Discrete Mode (autoMerge = false)

Intervals are stored separately even if they overlap, useful for audit trails, tracking individual operations, or maintaining historical records:


   IntervalSet<Integer> audit = new IntervalSet<>(false);  // discrete mode
   audit.add(1, 5);     // First verification
   audit.add(3, 8);     // Second verification (overlaps but kept separate)
   audit.add(10, 15);   // Third verification
   // Result: [1,5], [3,8], [10,15] - all intervals preserved for audit purposes
 

Important: Regardless of storage mode, all query APIs (contains(T), intervalContaining(T), navigation methods) work identically - they provide a unified logical view across all stored intervals. Only the internal storage representation differs.

Primary Client APIs

Basic Operations

Query and Navigation

Bulk Operations and Iteration

Introspection and Utilities

Supported Types

IntervalSet provides intelligent boundary calculation for interval splitting/merging operations across a wide range of types:

  • Numeric: Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal
  • Character: Character (Unicode-aware)
  • Temporal: Date, java.sql.Date, Time, Timestamp, Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, OffsetDateTime, OffsetTime, Duration
  • Custom: Any type implementing Comparable (with manual boundary handling if needed)

Thread Safety

IntervalSet is fully thread-safe with an optimized locking strategy:

  • Lock-free reads: All query operations (contains, navigation, iteration) require no locking
  • Minimal write locking: Only mutation operations acquire the internal ReentrantLock
  • Weakly consistent iteration: Iterators don't throw ConcurrentModificationException

Common Use Cases

Time Range Management


   IntervalSet<ZonedDateTime> schedule = new IntervalSet<>();
   schedule.add(meeting1Start, meeting1End);
   schedule.add(meeting2Start, meeting2End);

   if (schedule.contains(proposedMeetingTime)) {
       System.out.println("Time conflict detected");
   }
 

Numeric Range Tracking


   IntervalSet<Long> processedIds = new IntervalSet<>();
   processedIds.add(1000L, 1999L);    // First batch
   processedIds.add(2000L, 2999L);    // Second batch - automatically merges to [1000, 2999]

   Duration totalWork = processedIds.totalDuration((start, end) ->
       Duration.ofMillis(end - start + 1));
 

Audit Trail with Discrete Mode


   IntervalSet<LocalDate> auditLog = new IntervalSet<>(false);  // Keep all entries
   auditLog.add(verification1Start, verification1End);
   auditLog.add(verification2Start, verification2End);  // Overlaps preserved for audit

   // Query APIs still work across all intervals
   boolean dateVerified = auditLog.contains(targetDate);
 

Performance Characteristics

Most operations maintain O(log n) complexity regardless of storage mode. However, when autoMerge = false (discrete mode), certain query operations degrade to O(n) because they must search through potentially overlapping intervals:

AutoMerge Mode (autoMerge = true, default)

  • Add: O(log n) - May require merging adjacent intervals
  • Remove/RemoveRange: O(log n) - May require splitting intervals
  • Contains: O(log n) - Single floor lookup
  • IntervalContaining: O(log n) - Single floor lookup
  • Navigation: O(log n) - Leverages NavigableMap operations
  • Iteration: O(n) - Direct map iteration, no additional overhead

Discrete Mode (autoMerge = false)

  • Add: O(log n) - No merging, direct map insertion
  • Remove/RemoveRange: O(n) - Must iterate all intervals to find overlaps
  • Contains: O(n) - Must search backwards through overlapping intervals
  • IntervalContaining: O(n) - Must search backwards through overlapping intervals
  • Navigation: O(log n) - Leverages NavigableMap operations
  • Iteration: O(n) - Direct map iteration, no additional overhead
See Also:
  • Constructor Details

    • IntervalSet

      public IntervalSet()
      Creates a new IntervalSet with auto-merge enabled (default behavior). Overlapping intervals will be automatically merged when added.
    • IntervalSet

      public IntervalSet(boolean autoMerge)
      Creates a new IntervalSet with configurable merge behavior.
      Parameters:
      autoMerge - if true, overlapping intervals are merged automatically; if false, intervals are stored discretely but queries still work across all intervals
    • IntervalSet

      public IntervalSet(boolean autoMerge, Function<T,T> previousFunction, Function<T,T> nextFunction)
      Creates a new IntervalSet with configurable merge behavior and custom boundary functions.

      For custom types not supported by built-in previous/next logic, provide functions to compute the previous and next values. If null, falls back to built-in support. Users must provide these for non-built-in types to enable proper interval splitting during removals.

      Parameters:
      autoMerge - if true, overlapping intervals are merged automatically; if false, intervals are stored discretely but queries still work across all intervals
      previousFunction - custom function to compute previous value, or null for built-in
      nextFunction - custom function to compute next value, or null for built-in
    • IntervalSet

      public IntervalSet(IntervalSet<T> other)
      Copy constructor: creates a deep copy of the given IntervalSet, including mode, intervals, and custom functions.
      Parameters:
      other - the IntervalSet to copy
  • Method Details

    • add

      public void add(T start, T end)
      Add the inclusive interval [start,end]. Both start and end are inclusive.

      If autoMerge is true (default), overlapping intervals are merged automatically. When merging, if an interval with the same start key already exists, a union is performed using the maximum end value of both intervals. If autoMerge is false, intervals are stored discretely. If an interval with the same start key already exists, it will be replaced (last-one-wins behavior). Queries still work across all intervals to provide a unified view.

      AutoMerge Mode Examples:

      • Adding [1,5] then [1,8] results in [1,8] (union of overlapping intervals)
      • Adding [1,5] then [3,8] results in [1,8] (overlapping intervals merged)
      • Adding [1,5] then [1,3] results in [1,5] (smaller interval absorbed)

      Discrete Mode Examples:

      • Adding [1,5] then [1,8] results in [1,8] (second interval replaces first)
      • Adding [1,5] then [3,8] results in [1,5], [3,8] (both stored separately)

    • remove

      public void remove(T start, T end)
      Remove the inclusive interval [start,end], splitting existing intervals as needed. Both start and end are treated as inclusive bounds.

      If autoMerge is true, overlapping intervals are split where needed. If autoMerge is false, overlapping discrete intervals are removed entirely.

    • removeExact

      public boolean removeExact(T start, T end)
      Remove an exact interval [start, end] that matches a stored interval exactly.

      This operation acts only on a single stored interval whose start and end exactly match the specified values. No other intervals are merged, split, or trimmed as a result of this call. To remove a sub-range or to split existing intervals, use remove(T, T) or removeRange(T, T).

      Both start and end are treated as inclusive bounds. If no matching interval exists, the set remains unchanged. This method is thread-safe: it acquires the internal lock to perform removal under concurrent access but does not affect merging or splitting logic.

      Parameters:
      start - the inclusive start key of the interval to remove (must match exactly)
      end - the inclusive end key of the interval to remove (must match exactly)
      Returns:
      true if an interval with exactly this start and end was found and removed; false otherwise (no change to the set)
    • removeRange

      public void removeRange(T start, T end)
      Remove the inclusive range [start, end] from the set, trimming and splitting intervals as necessary.

      If autoMerge is true, intervals are trimmed and split as needed. If autoMerge is false, overlapping discrete intervals are removed entirely.

      For autoMerge=true, any stored interval that overlaps the removal range:

      • If an interval begins before start, its right boundary is trimmed to start.
      • If an interval ends after end, its left boundary is trimmed to end.
      • If an interval fully contains [start,end], it is split into two intervals: one covering [originalStart, start] and one covering [end, originalEnd].
      • Intervals entirely within [start,end] are removed.

      This operation is thread-safe: it acquires the internal write lock during mutation.

      Performance: O(log n) when autoMerge = true, but O(n) when autoMerge = false because all intervals must be checked for overlap.

      Parameters:
      start - inclusive start of the range to remove
      end - inclusive end of the range to remove
      Throws:
      IllegalArgumentException - if end < start
    • contains

      public boolean contains(T value)
      True if the value lies in any closed interval [start,end]. Both boundaries are inclusive.

      Performance: O(log n) when autoMerge = true, but O(n) when autoMerge = false due to the need to search through potentially overlapping discrete intervals.

    • intervalContaining

      public IntervalSet.Interval<T> intervalContaining(T value)
      Return the interval covering the specified value, or null if no interval contains it.

      Intervals are closed and inclusive on both ends ([start, end]), so a value v is contained in an interval if start <= v <= end . This method performs a lock-free read via ConcurrentSkipListMap.floorEntry(Object) and does not mutate the underlying set.

      In discrete mode, if multiple intervals contain the value, returns the one with the largest start key.

      Performance: O(log n) when autoMerge = true, but O(n) when autoMerge = false due to the need to search through potentially overlapping discrete intervals.

      Parameters:
      value - the non-null value to locate within stored intervals
      Returns:
      an IntervalSet.Interval whose start and end bracket value, or null if none
      Throws:
      NullPointerException - if value is null
    • asList

      public List<IntervalSet.Interval<T>> asList()
      Unmodifiable snapshot of all intervals (ordered).

      This method acquires the read lock to ensure a consistent snapshot of all intervals at the time of invocation.

      Returns:
      an unmodifiable list of all intervals in ascending order by start key
    • first

      public IntervalSet.Interval<T> first()
      Returns the first (lowest key) interval or null.
    • last

      public IntervalSet.Interval<T> last()
      Returns the last (highest key) interval or null.
    • nextInterval

      public IntervalSet.Interval<T> nextInterval(T value)
      Returns the next interval that contains the given value, or the next interval that starts after the value.

      If the value falls within an existing interval, that interval is returned. Otherwise, returns the next interval that starts after the value. Uses NavigableMap.floorEntry(Object) and NavigableMap.ceilingEntry(Object) for O(log n) performance.

      Parameters:
      value - the value to search from
      Returns:
      the interval containing the value or the next interval after it, or null if none
    • higherInterval

      public IntervalSet.Interval<T> higherInterval(T value)
      Returns the next interval that starts strictly after the given value, or null if none exists.

      This method uses NavigableMap.higherEntry(Object) for O(log n) performance.

      Parameters:
      value - the value to search from (exclusive)
      Returns:
      the next interval strictly after the value, or null if none
    • previousInterval

      public IntervalSet.Interval<T> previousInterval(T value)
      Returns the previous interval that starts at or before the given value, or null if none exists.

      This method uses NavigableMap.floorEntry(Object) for O(log n) performance. Note: This returns the interval by start key, not necessarily the interval containing the value. Use intervalContaining(T) to find the interval that actually contains a value.

      Parameters:
      value - the value to search from (inclusive)
      Returns:
      the previous interval at or before the value, or null if none
    • lowerInterval

      public IntervalSet.Interval<T> lowerInterval(T value)
      Returns the previous interval that starts strictly before the given value, or null if none exists.

      This method uses NavigableMap.lowerEntry(Object) for O(log n) performance.

      Parameters:
      value - the value to search from (exclusive)
      Returns:
      the previous interval strictly before the value, or null if none
    • getIntervalsInRange

      public List<IntervalSet.Interval<T>> getIntervalsInRange(T fromKey, T toKey)
      Returns all intervals whose start keys fall within the specified range [fromKey, toKey].

      This method uses NavigableMap.subMap(Object, boolean, Object, boolean) for efficient range queries. The returned list is ordered by start key.

      Parameters:
      fromKey - the start of the range (inclusive)
      toKey - the end of the range (inclusive)
      Returns:
      a list of intervals within the specified range, ordered by start key
      Throws:
      IllegalArgumentException - if fromKey > toKey
    • getIntervalsBefore

      public List<IntervalSet.Interval<T>> getIntervalsBefore(T toKey)
      Returns all intervals whose start keys are before the specified key.

      This method uses NavigableMap.headMap(Object, boolean) for efficient queries.

      Parameters:
      toKey - the upper bound (exclusive)
      Returns:
      a list of intervals before the specified key, ordered by start key
    • getIntervalsFrom

      public List<IntervalSet.Interval<T>> getIntervalsFrom(T fromKey)
      Returns all intervals whose start keys are at or after the specified key.

      This method uses NavigableMap.tailMap(Object, boolean) for efficient queries.

      Parameters:
      fromKey - the lower bound (inclusive)
      Returns:
      a list of intervals at or after the specified key, ordered by start key
    • descendingIterator

      public Iterator<IntervalSet.Interval<T>> descendingIterator()
      Returns an iterator over all intervals in descending order by start key.

      This method uses NavigableMap.descendingMap() for efficient reverse iteration. Like the standard iterator, this is weakly consistent and lock-free.

      Returns:
      an iterator over intervals in descending order by start key
    • keySet

      public NavigableSet<T> keySet()
      Returns a set of all start keys in the interval set.

      This method uses NavigableMap.navigableKeySet() to provide efficient key operations. The returned set supports range operations and is backed by the interval set.

      Returns:
      a navigable set of start keys
    • descendingKeySet

      public NavigableSet<T> descendingKeySet()
      Returns a set of all start keys in descending order.

      This method uses NavigableMap.descendingKeySet() for efficient reverse key iteration.

      Returns:
      a navigable set of start keys in descending order
    • removeIntervalsInKeyRange

      public int removeIntervalsInKeyRange(T fromKey, T toKey)
      Removes all intervals whose start keys fall within the specified range [fromKey, toKey].

      This method uses NavigableMap.subMap(Object, boolean, Object, boolean) for efficient bulk removal. This is more efficient than calling removeExact(T, T) multiple times.

      Parameters:
      fromKey - the start of the range (inclusive)
      toKey - the end of the range (inclusive)
      Returns:
      the number of intervals removed
      Throws:
      IllegalArgumentException - if fromKey > toKey
    • size

      public int size()
      Number of stored, non-overlapping intervals.
    • isEmpty

      public boolean isEmpty()
      Returns true if this set contains no intervals.
      Returns:
      true if this set contains no intervals
    • clear

      public void clear()
      Remove all stored intervals from the set.

      Thread-safe: acquires the write lock to clear the underlying map. After this call, size() returns 0, isEmpty() is true, and first() and last() return null.

    • totalDuration

      public Duration totalDuration()
      Compute the total covered duration across all stored intervals using a default mapping.

      This overload uses a default BiFunction based on the type of T: - If T is Temporal (and supports SECONDS unit, e.g., Instant, LocalDateTime, etc.), uses Duration.between(start, end). - If T is Number, computes (end.longValue() - start.longValue() + 1) and maps to Duration.ofNanos(diff) (arbitrary unit). - Otherwise, throws UnsupportedOperationException.

      For Temporal types like LocalDate that do not support SECONDS, this will throw DateTimeException. For custom or unsupported types, use the BiFunction overload. For numeric types, the unit (nanos) is arbitrary; use custom BiFunction for specific units.

      Returns:
      the sum of all interval durations
      Throws:
      UnsupportedOperationException - if no default mapping for type T
      DateTimeException - if Temporal type does not support Duration.between
      ArithmeticException - if numeric computation overflows long
    • totalDuration

      public Duration totalDuration(BiFunction<T,T,Duration> toDuration)
      Compute the total covered duration across all stored intervals.

      The caller provides a toDuration function that maps each interval's start and end values to a Duration. This method sums those Durations over all intervals in key order. For consistency under concurrent modifications, this method captures a snapshot of intervals at the time of invocation.

      Parameters:
      toDuration - a function that converts an interval [start, end] to a Duration
      Returns:
      the sum of all interval durations
    • iterator

      public Iterator<IntervalSet.Interval<T>> iterator()
      Returns an iterator over all stored intervals in ascending order by start key.

      This iterator is weakly consistent and lock-free, meaning it reflects live changes to the IntervalSet as they occur during iteration. The iterator does not throw ConcurrentModificationException and does not require external synchronization for reading.

      For strongly consistent iteration that captures a snapshot at call time, use asList().iterator() instead.

      Specified by:
      iterator in interface Iterable<T extends Comparable<? super T>>
      Returns:
      an iterator over the intervals in this set, ordered by start key
    • union

      public IntervalSet<T> union(IntervalSet<T> other)
      Returns a new IntervalSet that is the union of this set and the other.

      The result has the same autoMerge mode as this set. In discrete mode, if start keys duplicate, addition may throw IllegalArgumentException.

      Parameters:
      other - the other IntervalSet
      Returns:
      a new IntervalSet containing all intervals from both
    • intersection

      public IntervalSet<T> intersection(IntervalSet<T> other)
      Returns a new IntervalSet that is the intersection of this set and the other.

      The result has the same autoMerge mode as this set. Computes overlapping parts of intervals.

      Parameters:
      other - the other IntervalSet
      Returns:
      a new IntervalSet containing intersecting intervals
    • difference

      public IntervalSet<T> difference(IntervalSet<T> other)
      Returns a new IntervalSet that is the difference of this set minus the other.

      Equivalent to removing all intervals from other from this set. The result has the same autoMerge mode as this set.

      Parameters:
      other - the other IntervalSet to subtract
      Returns:
      a new IntervalSet with intervals from other removed
    • intersects

      public boolean intersects(IntervalSet<T> other)
      Returns true if this set intersects (overlaps) with the other set.
      Parameters:
      other - the other IntervalSet
      Returns:
      true if there is any overlap between intervals in this and other
    • equals

      public boolean equals(Object o)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object
    • toString

      public String toString()
      Overrides:
      toString in class Object