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
  • Auto-merging behavior - Overlapping intervals are automatically merged
  • 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-Merging Behavior

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


   IntervalSet<Integer> set = new IntervalSet<>();
   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]
 

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));
 

Performance Characteristics

All operations maintain O(log n) complexity:

  • 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
Author:
John DeRegnaucourt ([email protected])
Copyright (c) Cedar Software LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

License

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
See Also:
  • Constructor Details

    • IntervalSet

      public IntervalSet()
      Creates a new IntervalSet. Overlapping intervals will be automatically merged when added.
    • IntervalSet

      public IntervalSet(Function<T,T> previousFunction, Function<T,T> nextFunction)
      Creates a new IntervalSet with 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:
      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 intervals and custom functions.
      Parameters:
      other - the IntervalSet to copy
    • IntervalSet

      public IntervalSet(List<IntervalSet.Interval<T>> intervals)
      Creates a new IntervalSet from a list of intervals.

      This constructor enables JSON deserialization by allowing reconstruction of an IntervalSet from a previously serialized list of intervals. The intervals are added in order, with automatic merging of overlapping intervals as per normal IntervalSet behavior.

      This is typically used in conjunction with snapshot() for serialization workflows:

      
         // Serialize: get snapshot for JSON serialization
         List<Interval<T>> intervals = intervalSet.snapshot();
         // ... serialize intervals to JSON ...
         
         // Deserialize: reconstruct from JSON-deserialized list
         IntervalSet<T> restored = new IntervalSet<>(intervals);
       
      Parameters:
      intervals - the list of intervals to populate this set with
      Throws:
      NullPointerException - if intervals list or any interval is null
    • IntervalSet

      public IntervalSet(List<IntervalSet.Interval<T>> intervals, Function<T,T> previousFunction, Function<T,T> nextFunction)
      Creates a new IntervalSet from a list of intervals with custom boundary functions.

      This constructor enables JSON deserialization with custom discrete type support by allowing reconstruction of an IntervalSet from a previously serialized list of intervals along with the original boundary functions. This is essential when the IntervalSet was used with discrete types that require custom previous/next functions.

      This is typically used for discrete types not among the 20+ built-in types:

      
         // Original IntervalSet with custom functions for discrete type
         Function<MyType, MyType> prev = myType -> myType.previous();
         Function<MyType, MyType> next = myType -> myType.next();
         IntervalSet<MyType> original = new IntervalSet<>(prev, next);
         
         // Serialize: get snapshot for JSON serialization
         List<Interval<MyType>> intervals = original.snapshot();
         // ... serialize intervals and functions to JSON ...
         
         // Deserialize: reconstruct with original functions
         IntervalSet<MyType> restored = new IntervalSet<>(intervals, prev, next);
       
      Parameters:
      intervals - the list of intervals to populate this set with
      previousFunction - custom function to compute previous value, or null for built-in
      nextFunction - custom function to compute next value, or null for built-in
      Throws:
      NullPointerException - if intervals list or any interval is null
  • Method Details

    • add

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

      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.

      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)

    • 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.

      Overlapping intervals are split where needed.

    • 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.

      Intervals are trimmed and split as needed. 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)

      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)

    • 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.

      Performance: O(log n)

      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
    • 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.

    • snapshot

      public List<IntervalSet.Interval<T>> snapshot()
      Returns a snapshot copy of all intervals at the time of invocation.

      This method provides a consistent point-in-time view of all intervals by acquiring the internal write lock and creating a complete copy of the interval set. The returned list is completely independent of the original IntervalSet and will not reflect any subsequent modifications.

      This is useful when you need a stable view of intervals that won't change during processing, such as for bulk operations, reporting, analysis, or when integrating with code that expects stable collections.

      The returned list contains intervals in ascending order by start key, matching the iteration order of this IntervalSet.

      Thread Safety: This is the only "read" method that acquires the internal write lock to ensure the atomicity of the snapshot operation. All other query operations (contains, navigation, iteration) are lock-free. This method locks as a convenience to provide a guaranteed atomic snapshot rather than requiring users to manage external synchronization themselves.

      Performance: O(n) where n is the number of intervals. The method creates a new ArrayList with exact capacity and copies all interval objects.

      Memory: The returned list and its interval objects are completely independent copies. Modifying the returned list or the original IntervalSet will not affect the other.

      Returns:
      a new list containing copies of all intervals at the time of invocation, ordered by start key. Never returns null; returns empty list if no intervals.
    • 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). - If T is Date (or subclasses), computes Duration.ofMillis(end.getTime() - start.getTime()). - If T is Character, computes (end - start + 1) and maps to Duration.ofNanos(diff) (arbitrary unit). - If T is Duration, computes end.minus(start). - 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 and characters, 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. This method uses the underlying set's lock-free iterator and may reflect concurrent modifications made during iteration.

      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.

      The iterator reflects the state of the IntervalSet at the time of iteration and may see concurrent modifications.

      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.
      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.

      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.

      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.

      This method efficiently determines if any interval in this set overlaps with any interval in the other set. Two intervals overlap if they share at least one common value. This method provides an optimized check that avoids computing the full intersection.

      Performance: O(n + m) where n and m are the sizes of the two sets, using a two-pointer merge algorithm to detect overlap without building intermediate results.

      Examples:

      • [1,5] intersects with [3,8] → true (overlap: [3,5])
      • [1,5] intersects with [6,10] → false (no overlap)
      • [1,5] intersects with [5,10] → true (overlap at single point: 5)

      Parameters:
      other - the other IntervalSet to check for overlap
      Returns:
      true if there is any overlap between intervals in this and other sets
      Throws:
      NullPointerException - if other is null
    • 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