4 Copyright (c) 2018, Mungo Carstairs
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
10 * Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
13 * Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
17 * Neither the name of the copyright holder nor the names of its
18 contributors may be used to endorse or promote products derived from
19 this software without specific prior written permission.
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 package intervalstore.impl;
34 import java.util.AbstractCollection;
35 import java.util.ArrayList;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.NoSuchElementException;
40 import intervalstore.api.IntervalI;
41 import intervalstore.api.IntervalStoreI;
44 * A collection class to store interval-associated data, with O(log N)
45 * performance for overlap queries, insertion and deletion (where N is the size
46 * of the store). Accepts duplicate entries but not null values.
51 * any type providing <code>getBegin()</code> and <code>getEnd()</code>
53 public class IntervalStore<T extends IntervalI>
54 extends AbstractCollection<T> implements IntervalStoreI<T>
57 * An iterator over the intervals held in this store, with no particular
58 * ordering guaranteed. The iterator does not support the optional
59 * <code>remove</code> operation (throws
60 * <code>UnsupportedOperationException</code> if attempted).
66 private class IntervalIterator<V extends IntervalI> implements Iterator<V>
69 * iterator over top level non-nested intervals
71 Iterator<? extends IntervalI> topLevelIterator;
74 * iterator over NCList (if any)
76 Iterator<? extends IntervalI> nestedIterator;
79 * Constructor initialises iterators over the top level list and any nested
82 * @param intervalStore
84 public IntervalIterator(
85 IntervalStore<? extends IntervalI> intervalStore)
87 topLevelIterator = nonNested.iterator();
90 nestedIterator = nested.iterator();
95 public boolean hasNext()
97 return topLevelIterator.hasNext() ? true
98 : (nestedIterator != null && nestedIterator.hasNext());
101 @SuppressWarnings("unchecked")
105 if (topLevelIterator.hasNext())
107 return (V) topLevelIterator.next();
109 if (nestedIterator != null)
111 return (V) nestedIterator.next();
113 throw new NoSuchElementException();
118 private List<T> nonNested;
120 private NCList<T> nested;
125 public IntervalStore()
127 nonNested = new ArrayList<>();
131 * Constructor given a list of intervals. Note that the list may get sorted as
132 * a side-effect of calling this constructor.
134 public IntervalStore(List<T> intervals)
139 * partition into subranges whose root intervals
140 * have no mutual containment (if no intervals are nested,
141 * each subrange is of length 1 i.e. a single interval)
143 List<IntervalI> sublists = new NCListBuilder<T>()
144 .partitionNestedSublists(intervals);
147 * add all 'subrange root intervals' (and any co-located intervals)
148 * to our top level list of 'non-nested' intervals;
149 * put aside any left over for our NCList
151 List<T> nested = new ArrayList<>();
153 for (IntervalI subrange : sublists)
155 int listIndex = subrange.getBegin();
156 IntervalI root = intervals.get(listIndex);
157 while (listIndex <= subrange.getEnd())
159 T t = intervals.get(listIndex);
160 if (root.equalsInterval(t))
172 if (!nested.isEmpty())
174 this.nested = new NCList<>(nested);
179 * Adds one interval to the store.
184 public boolean add(T interval)
186 if (interval == null)
190 if (!addNonNestedInterval(interval))
193 * detected a nested interval - put it in the NCList structure
195 addNestedInterval(interval);
201 public boolean contains(Object entry)
203 if (listContains(nonNested, entry))
208 return nested == null ? false : nested.contains(entry);
211 protected boolean addNonNestedInterval(T entry)
213 synchronized (nonNested)
216 * find the first stored interval which doesn't precede the new one
218 int insertPosition = BinarySearcher.findFirst(nonNested,
219 val -> val.getBegin() >= entry.getBegin());
221 * fail if we detect interval enclosure
222 * - of the new interval by the one before or after it
223 * - of the next interval by the new one
225 if (insertPosition > 0)
227 if (nonNested.get(insertPosition - 1).properlyContainsInterval(entry))
232 if (insertPosition < nonNested.size())
234 T following = nonNested.get(insertPosition);
235 if (entry.properlyContainsInterval(following)
236 || following.properlyContainsInterval(entry))
243 * checks passed - add the interval
245 nonNested.add(insertPosition, entry);
252 public List<T> findOverlaps(long from, long to)
254 List<T> result = new ArrayList<>();
256 findNonNestedOverlaps(from, to, result);
260 result.addAll(nested.findOverlaps(from, to));
267 public String prettyPrint()
269 String pp = nonNested.toString();
272 pp += System.lineSeparator() + nested.prettyPrint();
278 public boolean isValid()
280 for (int i = 0; i < nonNested.size() - 1; i++)
282 IntervalI i1 = nonNested.get(i);
283 IntervalI i2 = nonNested.get(i + 1);
285 if (i2.getBegin() < i1.getBegin())
287 System.err.println("nonNested wrong start order : " + i1.toString()
288 + ", " + i2.toString());
291 if (i1.properlyContainsInterval(i2)
292 || i2.properlyContainsInterval(i1))
294 System.err.println("nonNested invalid containment!: "
296 + ", " + i2.toString());
300 return nested == null ? true : nested.isValid();
306 int i = nonNested.size();
315 public synchronized boolean remove(Object o)
323 @SuppressWarnings("unchecked")
327 * try the non-nested positional intervals first
329 boolean removed = removeNonNested(entry);
332 * if not found, try nested intervals
334 if (!removed && nested != null)
336 removed = nested.remove(entry);
340 } catch (ClassCastException e)
347 * Removes the given entry from the list of non-nested entries, returning true
348 * if found and removed, or false if not found. Specifically, removes the
349 * first item in the list for which <code>item.equals(entry)</code>.
354 protected boolean removeNonNested(T entry)
357 * find the first interval that might match, i.e. whose
358 * start position is not less than the target range start
359 * (NB inequality test ensures the first match if any is found)
361 int startIndex = BinarySearcher.findFirst(nonNested,
362 val -> val.getBegin() >= entry.getBegin());
365 * traverse intervals to look for a match
367 int from = entry.getBegin();
369 int size = nonNested.size();
372 T sf = nonNested.get(i);
373 if (sf.getBegin() > from)
377 if (sf.equals(entry))
388 public int getDepth()
394 return (nonNested.isEmpty() ? 0 : 1)
395 + (nested == null ? 0 : nested.getDepth());
399 * Adds one interval to the NCList that can manage nested intervals (creating
400 * the NCList if necessary)
402 protected synchronized void addNestedInterval(T interval)
406 nested = new NCList<>();
408 nested.add(interval);
412 * Answers true if the list contains the interval, else false. This method is
413 * optimised for the condition that the list is sorted on interval start
414 * position ascending, and will give unreliable results if this does not hold.
420 protected boolean listContains(List<T> intervals, Object entry)
422 if (intervals == null || entry == null || !(entry instanceof IntervalI))
427 IntervalI interval = (IntervalI) entry;
430 * locate the first entry in the list which does not precede the interval
432 int pos = BinarySearcher.findFirst(intervals,
433 val -> val.getBegin() >= interval.getBegin());
434 int len = intervals.size();
437 T sf = intervals.get(pos);
438 if (sf.getBegin() > interval.getBegin())
440 return false; // no match found
442 if (sf.equals(interval))
452 * Answers an iterator over the intervals in the store, with no particular
453 * ordering guaranteed. The iterator does not support the optional
454 * <code>remove</code> operation (throws
455 * <code>UnsupportedOperationException</code> if attempted).
458 public Iterator<T> iterator()
460 return new IntervalIterator<>(this);
466 this.nonNested.clear();
467 this.nested = new NCList<>();
471 * Adds non-nested intervals to the result list that lie within the target
478 protected void findNonNestedOverlaps(long from, long to,
482 * find the first interval whose end position is
483 * after the target range start
485 int startIndex = BinarySearcher.findFirst(nonNested,
486 val -> val.getEnd() >= from);
488 final int startIndex1 = startIndex;
490 while (i < nonNested.size())
492 T sf = nonNested.get(i);
493 if (sf.getBegin() > to)
497 if (sf.getBegin() <= to && sf.getEnd() >= from)
506 public String toString()
508 String s = nonNested.toString();
511 s = s + System.lineSeparator() + nested.toString();
517 public int getWidth()
519 // TODO Auto-generated method stub
524 public List<T> findOverlaps(long start, long end, List<T> result)
526 // TODO Auto-generated method stub
531 public boolean revalidate()
533 // TODO Auto-generated method stub
538 public IntervalI get(int i)
540 // TODO Auto-generated method stub