Skip to content

Commit 7a7521c

Browse files
jderegclaude
andcommitted
Add future IntervalMap design doc
Documents the planned IntervalMap<K, V> class for mapping intervals to values, including API design, internal storage model, and n-cube Axis.java migration plan to replace Guava TreeRangeMap. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 85763d8 commit 7a7521c

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

docs/future-IntervalMap-plan.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Future: IntervalMap<K, V> for java-util + Guava TreeRangeMap Replacement
2+
3+
*Drafted: 2026-02-06. Deferred for future implementation.*
4+
5+
## Summary
6+
7+
Add `IntervalMap<K, V>` to java-util as a thread-safe map from non-overlapping intervals to values. This would allow replacing Guava's `TreeRangeMap` in n-cube's `Axis.java`, further reducing the Guava dependency surface.
8+
9+
## Motivation
10+
11+
After replacing Guava's Joiner/Splitter/Iterables in n-cube with JDK equivalents (commit `83c10d8e`), the remaining Guava surface area is:
12+
1. **TreeRangeMap** in `Axis.java` - maps intervals to Column objects for RANGE/SET axes
13+
2. **Cache** in `GCacheManager.java` / `GuavaCache.java` - keeping for now
14+
15+
IntervalMap would also be useful for general-purpose interval-to-value mappings (e.g., partition tracking with metadata, time-range scheduling with associated data).
16+
17+
## Why IntervalSet Cannot Wrap IntervalMap
18+
19+
We explored whether `IntervalSet` could be simplified to wrap `IntervalMap` (like `CompactSet` wraps `CompactMap`). The answer is **no**:
20+
21+
- **IntervalSet auto-merges**: Adding [1,5) then [3,8) merges to [1,8). This is core to its semantics.
22+
- **IntervalMap cannot auto-merge**: [1,5)->A and [3,8)->B cannot merge because A != B.
23+
- IntervalSet's merge logic is tightly coupled to `ConcurrentSkipListMap` operations. Wrapping would either break encapsulation or duplicate all merge logic with no code reuse.
24+
- **Decision**: IntervalMap should be a standalone class sharing design patterns with IntervalSet.
25+
26+
## Standard Interface Fit Analysis
27+
28+
Neither class maps cleanly to standard Java collection interfaces:
29+
30+
- **IntervalMap vs NavigableMap/SortedMap**: Map interfaces have one key type for both storage and lookup. IntervalMap stores by interval (two bounds) but looks up by point (one value). `put(K, V)` can't express an interval.
31+
- **IntervalSet vs NavigableSet/SortedSet**: Auto-merge violates Set's contract. After `add(a)` then `add(b)`, the set may contain neither original element.
32+
- **Good fits**: `Iterable` (both), `Serializable` (both), `Function<K, V>` (IntervalMap point lookup), `Predicate<T>` (IntervalSet point containment).
33+
34+
## IntervalMap<K, V> Design
35+
36+
### Key Requirement: Point Intervals
37+
38+
n-cube's SET axis stores **both** half-open ranges [low, high) and discrete point values. Currently handled via Guava's `Range.closedOpen()` and `Range.closed()`. IntervalMap must support both:
39+
- `put(start, end, value)` for half-open [start, end) ranges
40+
- `putPoint(point, value)` for discrete point values
41+
42+
### Internal Storage
43+
44+
```java
45+
private final ConcurrentSkipListMap<K, Node<K, V>> intervals = new ConcurrentSkipListMap<>();
46+
private final transient ReentrantLock lock = new ReentrantLock();
47+
48+
private static class Node<K, V> {
49+
final K end; // null for point intervals
50+
final V value;
51+
}
52+
```
53+
54+
### Proposed API
55+
56+
```java
57+
public class IntervalMap<K extends Comparable<? super K>, V> implements Iterable<IntervalMap.Entry<K, V>> {
58+
59+
public static final class Entry<K extends Comparable<? super K>, V> {
60+
// start, end (null for points), value
61+
// isPoint(), getStart(), getEnd(), getValue()
62+
}
63+
64+
// Mutations (write-locked)
65+
void put(K start, K end, V value); // [start, end) range
66+
void putPoint(K point, V value); // exact point
67+
boolean remove(K start, K end); // remove exact range
68+
boolean removePoint(K point); // remove exact point
69+
void clear();
70+
71+
// Point lookup (lock-free)
72+
V get(K point); // searches both ranges and points
73+
74+
// Overlap queries (lock-free) -- replaces Guava's subRangeMap()
75+
boolean overlaps(K start, K end); // any entry overlaps [start, end)?
76+
boolean containsPoint(K point); // any entry contains this point?
77+
List<Entry<K, V>> getOverlapping(K start, K end);
78+
79+
// Iteration (lock-free, weakly consistent)
80+
Iterator<Entry<K, V>> iterator(); // all entries ordered by start
81+
List<Entry<K, V>> snapshot(); // atomic copy
82+
int size();
83+
boolean isEmpty();
84+
}
85+
```
86+
87+
### Core Algorithm: get(K point)
88+
1. `floorEntry(point)` -- find largest key <= point
89+
2. If node.end == null -- point interval, return value only if exact match
90+
3. If node.end != null -- range [start, end), return value if end > point
91+
92+
### Core Algorithm: overlaps(K start, K end)
93+
1. Check `floorEntry(start)` -- a range starting before `start` might extend into [start, end)
94+
2. Check all entries in `subMap(start, false, end, false)` -- any entry starting within (start, end) overlaps
95+
3. For point entries, check if point falls within [start, end)
96+
97+
## n-cube Axis.java Migration
98+
99+
**File**: `src/main/java/com/cedarsoftware/ncube/Axis.java`
100+
101+
Axis.java is the **only file** in n-cube using TreeRangeMap. The migration involves 13 localized changes:
102+
103+
### Current Guava Usage
104+
105+
| Operation | Line(s) | Purpose | IntervalMap Replacement |
106+
|-----------|---------|---------|------------------------|
107+
| `TreeRangeMap.create()` | 110 | Field init | `new IntervalMap<>()` |
108+
| `put(Range, Column)` | 618, 620-625 | Index column | `put(low, high, col)` / `putPoint(val, col)` |
109+
| `get(point)` | 1739 | Point lookup | `get(point)` (same signature) |
110+
| `remove(Range)` | 1093, 1095-1100 | Deindex column | `remove(low, high)` / `removePoint(val)` |
111+
| `clear()` | 829-830 | Clear indexes | `clear()` (same) |
112+
| `subRangeMap()` | 1867, 1882 | Overlap detection | `overlaps(low, high)` / `containsPoint(val)` |
113+
| `asMapOfRanges().values()` | 1922 | Get unique columns | `for (Entry e : map)` + LinkedHashSet |
114+
| `asMapOfRanges().size()` | 1997 | Size (testing) | `size()` |
115+
| `asMapOfRanges().forEach()` | 2055-2059 | Iterate entries | `for (Entry e : map)` |
116+
117+
### Key Migration Details
118+
119+
**indexColumn() -- SET axis** needs to distinguish Range from discrete:
120+
```java
121+
// Before:
122+
rangeToCol.put(valueToRange(elem), column);
123+
124+
// After:
125+
if (elem instanceof com.cedarsoftware.ncube.Range range) {
126+
rangeToCol.put(range.getLow(), range.getHigh(), column);
127+
} else {
128+
rangeToCol.putPoint(elem, column);
129+
}
130+
```
131+
132+
**doesOverlap()** simplifies significantly:
133+
```java
134+
// Before (3 lines):
135+
RangeMap<Comparable, Column> ranges = rangeToCol.subRangeMap(valueToRange(range));
136+
return !ranges.asMapOfRanges().isEmpty();
137+
138+
// After (1 line):
139+
return rangeToCol.overlaps(range.getLow(), range.getHigh());
140+
```
141+
142+
**valueToRange() helper** (lines 1844-1856) can be deleted entirely -- conversion is inlined at each call site.
143+
144+
### Post-Migration Guava Status
145+
146+
After this migration, only `GCacheManager.java` and `GuavaCache.java` would reference Guava (for Cache). The `com.google.common.collect.Range`, `RangeMap`, and `TreeRangeMap` imports would be fully eliminated.
147+
148+
## Implementation Sequence
149+
150+
1. Create `IntervalMap.java` in java-util (~600-800 lines)
151+
2. Create `IntervalMapTest.java` with comprehensive tests
152+
3. Update java-util changelog, release
153+
4. Update n-cube's java-util dependency version
154+
5. Apply 13 changes to Axis.java, delete `valueToRange()`
155+
6. Run full n-cube test suite (`./gradlew clean test`)
156+
7. Verify no Guava Range/RangeMap imports remain
157+
158+
## Verification Criteria
159+
160+
1. All java-util tests pass, IntervalMapTest >90% coverage
161+
2. All n-cube tests pass after migration
162+
3. `grep -r "com.google.common.collect.Range" src/main/` returns no hits

0 commit comments

Comments
 (0)