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,
220 BinarySearcher.fbegin);
222 * fail if we detect interval enclosure
223 * - of the new interval by the one before or after it
224 * - of the next interval by the new one
226 if (insertPosition > 0)
228 if (nonNested.get(insertPosition - 1)
229 .properlyContainsInterval(entry))
234 if (insertPosition < nonNested.size())
236 T following = nonNested.get(insertPosition);
237 if (entry.properlyContainsInterval(following)
238 || following.properlyContainsInterval(entry))
245 * checks passed - add the interval
247 nonNested.add(insertPosition, entry);
254 public List<T> findOverlaps(long from, long to)
256 List<T> result = new ArrayList<>();
258 findNonNestedOverlaps(from, to, result);
262 nested.findOverlaps(from, to, result);
269 public String prettyPrint()
271 String pp = nonNested.toString();
274 pp += '\n' + nested.prettyPrint();
280 public boolean isValid()
282 for (int i = 0; i < nonNested.size() - 1; i++)
284 IntervalI i1 = nonNested.get(i);
285 IntervalI i2 = nonNested.get(i + 1);
287 if (i2.getBegin() < i1.getBegin())
289 System.err.println("nonNested wrong start order : " + i1.toString()
290 + ", " + i2.toString());
293 if (i1.properlyContainsInterval(i2)
294 || i2.properlyContainsInterval(i1))
296 System.err.println("nonNested invalid containment!: "
298 + ", " + i2.toString());
302 return nested == null ? true : nested.isValid();
308 int i = nonNested.size();
317 public synchronized boolean remove(Object o)
325 @SuppressWarnings("unchecked")
329 * try the non-nested positional intervals first
331 boolean removed = removeNonNested(entry);
334 * if not found, try nested intervals
336 if (!removed && nested != null)
338 removed = nested.remove(entry);
342 } catch (ClassCastException e)
349 * Removes the given entry from the list of non-nested entries, returning true
350 * if found and removed, or false if not found. Specifically, removes the
351 * first item in the list for which <code>item.equals(entry)</code>.
356 protected boolean removeNonNested(T entry)
359 * find the first interval that might match, i.e. whose
360 * start position is not less than the target range start
361 * (NB inequality test ensures the first match if any is found)
363 int from = entry.getBegin();
364 int startIndex = BinarySearcher.findFirst(nonNested, from,
365 BinarySearcher.fbegin);
368 * traverse intervals to look for a match
372 int size = nonNested.size();
375 T sf = nonNested.get(i);
376 if (sf.getBegin() > from)
380 if (sf.equals(entry))
391 public int getDepth()
397 return (nonNested.isEmpty() ? 0 : 1)
398 + (nested == null ? 0 : nested.getDepth());
402 * Adds one interval to the NCList that can manage nested intervals (creating
403 * the NCList if necessary)
405 protected synchronized void addNestedInterval(T interval)
409 nested = new NCList<>();
411 nested.add(interval);
415 * Answers true if the list contains the interval, else false. This method is
416 * optimised for the condition that the list is sorted on interval start
417 * position ascending, and will give unreliable results if this does not hold.
423 protected boolean listContains(List<T> intervals, Object entry)
425 if (intervals == null || entry == null || !(entry instanceof IntervalI))
430 IntervalI interval = (IntervalI) entry;
433 * locate the first entry in the list which does not precede the interval
435 int from = interval.getBegin();
436 int pos = BinarySearcher.findFirst(intervals, from,
437 BinarySearcher.fbegin);
438 int len = intervals.size();
441 T sf = intervals.get(pos);
442 if (sf.getBegin() > from)
444 return false; // no match found
446 if (sf.equals(interval))
456 * Answers an iterator over the intervals in the store, with no particular
457 * ordering guaranteed. The iterator does not support the optional
458 * <code>remove</code> operation (throws
459 * <code>UnsupportedOperationException</code> if attempted).
462 public Iterator<T> iterator()
464 return new IntervalIterator<>(this);
470 this.nonNested.clear();
471 this.nested = new NCList<>();
475 * Adds non-nested intervals to the result list that lie within the target
482 protected void findNonNestedOverlaps(long from, long to,
486 * find the first interval whose end position is
487 * after the target range start
489 int startIndex = BinarySearcher.findFirst(nonNested, (int) from,
490 BinarySearcher.fend);
491 for (int i = startIndex, n = nonNested.size(); i < n; i++)
493 T sf = nonNested.get(i);
494 if (sf.getBegin() > to)
498 if (sf.getEnd() >= from)
506 public String toString()
508 String s = nonNested.toString();
511 s = s + '\n'// + System.lineSeparator()
518 public int getWidth()
520 return (nonNested == null ? 0 : nonNested.size())
521 + (nested == null ? 0 : nested.size());
525 public List<T> findOverlaps(long start, long end, List<T> result)
527 return findOverlaps(start, end);
531 public boolean revalidate()
538 public IntervalI get(int i)
540 // not supported (but could be)
545 public boolean canCheckForDuplicates()
551 public boolean add(T interval, boolean checkForDuplicate)
553 return add(interval);