-/*
-BSD 3-Clause License
-
-Copyright (c) 2018, Mungo Carstairs
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-* Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-package intervalstore.nonc;
-
-import java.util.AbstractCollection;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
-import intervalstore.api.IntervalI;
-
-/**
- * A collection class to store interval-associated data, with O(log N)
- * performance for overlap queries, insertion and deletion (where N is the size
- * of the store). Accepts duplicate entries but not null values.
- *
- * @author gmcarstairs
- *
- * @param <T>
- * any type providing <code>getBegin()</code> and <code>getEnd()</code>
- */
-public class IntervalStore<T extends IntervalI>
- extends AbstractCollection<T>
- implements intervalstore.api.IntervalStoreI<T>
-{
- private List<T> intervals;
-
- private boolean isTainted;
-
- private IntervalI[] orderedIntervalStarts;
-
- /**
- * Constructor
- */
- public IntervalStore()
- {
- intervals = new ArrayList<>();
- }
-
- class IntervalComparator implements Comparator<T>
- {
-
- @Override
- public int compare(IntervalI o1, IntervalI o2)
- {
-
- int order = Integer.compare(o1.getBegin(), o2.getBegin());
- if (order == 0)
- {
- /*
- * if tied on start position, longer length sorts to left
- * i.e. the negation of normal ordering by length
- */
- order = Integer.compare(o2.getEnd(), o1.getEnd());
- }
- return order;
-
- }
-
- };
-
- /**
- * Constructor given a list of intervals. Note that the list may get sorted as
- * a side-effect of calling this constructor.
- */
- public IntervalStore(List<T> intervals)
- {
- Collections.sort(this.intervals = intervals,
- new IntervalComparator());
- isTainted = true;
- findOverlaps(0, -1); // ensure this is ordered
- }
-
- /**
- * Adds one interval to the store.
- *
- * @param interval
- */
- @Override
- public boolean add(T interval)
- {
- if (interval == null)
- {
- return false;
- }
-
- synchronized (intervals)
- {
- int insertPosition = findFirstBegin(intervals, interval.getBegin());
- intervals.add(insertPosition, interval);
- isTainted = true;
- return true;
- }
- }
-
- @Override
- public void clear()
- {
- intervals.clear();
- orderedIntervalStarts = null;
- isTainted = true;
- }
-
- @Override
- public boolean contains(Object entry)
- {
- return listContains(intervals, entry);
- }
-
- protected int findFirstBegin(List<T> list, long pos)
- {
- int start = 0;
- int end = list.size() - 1;
- int matched = list.size();
-
- while (start <= end)
- {
- int mid = (start + end) / 2;
- if (list.get(mid).getBegin() >= pos)
- {
- matched = mid;
- end = mid - 1;
- }
- else
- {
- start = mid + 1;
- }
- }
- return matched;
- }
-
- protected int findFirstEnd(List<T> list, long pos)
- {
- int start = 0;
- int end = list.size() - 1;
- int matched = list.size();
-
- while (start <= end)
- {
- int mid = (start + end) / 2;
- if (list.get(mid).getEnd() >= pos)
- {
- matched = mid;
- end = mid - 1;
- }
- else
- {
- start = mid + 1;
- }
- }
- return matched;
- }
-
- /**
- * Adds non-nested intervals to the result list that lie within the target
- * range
- *
- * @param from
- * @param to
- * @param result
- */
- protected void findIntervalOverlaps(long from, long to,
- List<T> result)
- {
-
- int startIndex = findFirstEnd(intervals, from);
- final int startIndex1 = startIndex;
- int i = startIndex1;
- while (i < intervals.size())
- {
- T sf = intervals.get(i);
- if (sf.getBegin() > to)
- {
- break;
- }
- if (sf.getBegin() <= to && sf.getEnd() >= from)
- {
- result.add(sf);
- }
- i++;
- }
- }
-
- @Override
- public List<T> findOverlaps(long start, long end)
- {
-
- List<T> result = new ArrayList<>();
-
- int n = intervals.size();
- switch (n)
- {
- case 0:
- return result;
- case 1:
- justCheckOne(intervals.get(0), start, end, result);
- return result;
- default:
-
- if (isTainted)
- {
- orderedIntervalStarts = intervals.toArray(new IntervalI[0]);
- linkFeatures(orderedIntervalStarts);
- isTainted = false;
- }
- break;
- }
-
- if (end < start)
- {
- // just ensuring not tainted -- fully loaded
- return result;
- }
-
- // (1) Find the closest feature to this position.
-
- int index = getClosestFeature(orderedIntervalStarts, start);
- IntervalI sf = (index < 0 ? null : orderedIntervalStarts[index]);
-
- // (2) Traverse the containedBy field, checking for overlap.
-
- while (sf != null)
- {
- if (sf.getEnd() >= start)
- {
- result.add((T) sf);
- }
- sf = sf.getContainedBy();
- }
-
- // (3) For an interval, find the last feature that starts in this interval,
- // and add all features up through that feature.
-
- if (end >= start)
- {
- // fill in with all features that start within this interval, fully
- // inclusive
- int index2 = getClosestFeature(orderedIntervalStarts, end);
- while (++index <= index2)
- {
- result.add((T) orderedIntervalStarts[index]);
- }
-
- }
- return result;
- }
-
- private int getClosestFeature(IntervalI[] l, long pos)
- {
- int low = 0;
- int high = l.length - 1;
- while (low <= high)
- {
- int mid = (low + high) >>> 1;
- IntervalI f = l[mid];
- switch (Long.signum(f.getBegin() - pos))
- {
- case -1:
- low = mid + 1;
- continue;
- case 1:
- high = mid - 1;
- continue;
- case 0:
-
- while (++mid <= high && l[mid].getBegin() == pos)
- {
- ;
- }
- return --mid;
- }
- }
- return (high < 0 ? -1 : high);
- }
-
- @SuppressWarnings("unchecked")
- public T getContainedBy(T sf, T sf0)
- {
- int begin = sf0.getBegin();
- while (sf != null)
- {
- if (begin <= sf.getEnd())
- {
- // System.out.println("\nIS found " + sf0.getIndex1() + ":" + sf0
- // + "\nFS in " + sf.getIndex1() + ":" + sf);
- return sf;
- }
- sf = (T) sf.getContainedBy();
- }
- return null;
- }
-
- @Override
- public int getDepth()
- {
- if (size() == 0)
- {
- return 0;
- }
- int maxDepth = 0;
- for (int i = intervals.size(); --i >= 0;)
- {
- T element = intervals.get(i);
- T container = element;
- int depth = 0;
- while ((container = (T) container.getContainedBy()) != null)
- {
- if (++depth > maxDepth && container == this)
- {
- maxDepth = depth;
- break;
- }
- }
- }
- return maxDepth;
- }
-
- @Override
- public boolean isValid()
- {
- for (int i = 0; i < intervals.size() - 1; i++)
- {
- T i1 = intervals.get(i);
- T i2 = intervals.get(i + 1);
-
- if (i2.getBegin() < i1.getBegin())
- {
- System.err.println("nonNested wrong start order : " + i1.toString()
- + ", " + i2.toString());
- return false;
- }
- }
- return true;
- }
-
- /**
- * Answers an iterator over the intervals in the store, with no particular
- * ordering guaranteed. The iterator does not support the optional
- * <code>remove</code> operation (throws
- * <code>UnsupportedOperationException</code> if attempted).
- */
- @Override
- public Iterator<T> iterator()
- {
- return intervals.iterator();
-
- }
-
- private void justCheckOne(T sf, long start, long end, List<T> result)
- {
- if (sf.getBegin() <= end && sf.getEnd() >= start)
- {
- result.add(sf);
- }
- return;
- }
-
- private void linkFeatures(IntervalI[] features)
- {
- int n = features.length;
- switch (n)
- {
- case 0:
- return;
- case 1:
- features[0].setIndex1(1);
- return;
- }
- int maxEnd = features[0].getEnd();
- for (int i = 1; i < n;)
- {
- T sf = (T) features[i];
- sf.setIndex1(++i);
- if (sf.getBegin() <= maxEnd)
- {
- sf.setContainedBy(getContainedBy((T) features[i - 2], sf));
- }
- if (sf.getEnd() > maxEnd)
- {
- maxEnd = sf.getEnd();
- }
- }
-
- }
-
- /**
- * Answers true if the list contains the interval, else false. This method is
- * optimised for the condition that the list is sorted on interval start
- * position ascending, and will give unreliable results if this does not hold.
- *
- * @param intervals
- * @param entry
- * @return
- */
- protected boolean listContains(List<T> intervals, Object entry)
- {
- if (intervals == null || entry == null)
- {
- return false;
- }
-
- T interval = (T) entry;
-
- /*
- * locate the first entry in the list which does not precede the interval
- */
- int pos = findFirstBegin(intervals, interval.getBegin());
- int len = intervals.size();
- while (pos < len)
- {
- T sf = intervals.get(pos);
- if (sf.getBegin() > interval.getBegin())
- {
- return false; // no match found
- }
- if (sf.getEnd() == interval.getEnd() && sf.equals(interval))
- {
- return true;
- }
- pos++;
- }
- return false;
- }
-
- @Override
- public String prettyPrint()
- {
- return toString();
- }
-
- @Override
- public synchronized boolean remove(Object o)
- {
- if (o == null || !(o instanceof IntervalI))
- {
- return false;
- }
-
- T entry = (T) o;
-
- /*
- * try the non-nested positional intervals first
- */
- boolean removed = removeInterval(entry);
- if (removed)
- {
- isTainted = true;
- }
-
- return removed;
-
- }
-
- /**
- * Removes the given entry from the list of non-nested entries, returning true
- * if found and removed, or false if not found. Specifically, removes the
- * first item in the list for which <code>item.equals(entry)</code>.
- *
- * @param entry
- * @return
- */
- protected boolean removeInterval(T entry)
- {
- /*
- * find the first interval that might match, i.e. whose
- * start position is not less than the target range start
- * (NB inequality test ensures the first match if any is found)
- */
- int startIndex = findFirstBegin(intervals, entry.getBegin());
-
- /*
- * traverse intervals to look for a match
- */
- int from = entry.getBegin();
- int i = startIndex;
- int size = intervals.size();
- while (i < size)
- {
- T sf = intervals.get(i);
- if (sf.getBegin() > from)
- {
- break;
- }
- if (sf.equals(entry))
- {
- intervals.remove(i);
- return true;
- }
- i++;
- }
- return false;
- }
-
- @Override
- public int size()
- {
- return intervals.size();
- }
-
- @Override
- public String toString()
- {
- ensureFinalized();
- StringBuffer sb = new StringBuffer();
-
- return sb.toString();
- }
-
-}