JAL-3397 final update
[jalview.git] / src / intervalstore / impl / NCList.java
1 /*
2 BSD 3-Clause License
3
4 Copyright (c) 2018, Mungo Carstairs
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9
10 * Redistributions of source code must retain the above copyright notice, this
11   list of conditions and the following disclaimer.
12
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.
16
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.
20
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.
31 */
32 package intervalstore.impl;
33
34 import java.util.AbstractCollection;
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.NoSuchElementException;
40
41 import intervalstore.api.IntervalI;
42
43 /**
44  * An adapted implementation of NCList as described in the paper
45  * 
46  * <pre>
47  * Nested Containment List (NCList): a new algorithm for accelerating
48  * interval query of genome alignment and interval databases
49  * - Alexander V. Alekseyenko, Christopher J. Lee
50  * https://doi.org/10.1093/bioinformatics/btl647
51  * </pre>
52  */
53 public class NCList<T extends IntervalI> extends AbstractCollection<T>
54 {
55
56   // private static final boolean OPTION_FIND_ANY = false;
57
58   /**
59    * A depth-first iterator over the elements stored in the NCList
60    */
61   private class NCListIterator implements Iterator<T>
62   {
63     int subrangeIndex;
64
65     Iterator<T> nodeIterator;
66
67     /**
68      * Constructor bootstraps a pointer to an iterator over the first subrange
69      * (if any)
70      */
71     NCListIterator()
72     {
73       subrangeIndex = nextSubrange(-1);
74     }
75
76     /**
77      * Moves the subrange iterator to the next subrange, and answers its index
78      * in the list of subranges. If there are no more, sets the iterator to null
79      * and answers -1.
80      * 
81      * @return
82      */
83     private int nextSubrange(int after)
84     {
85       int nextIndex = after + 1;
86       if (nextIndex < subranges.size())
87       {
88         nodeIterator = subranges.get(nextIndex).iterator();
89         return nextIndex;
90       }
91       nodeIterator = null;
92       return -1;
93     }
94
95     @Override
96     public boolean hasNext()
97     {
98       return nodeIterator != null && nodeIterator.hasNext();
99     }
100
101     /**
102      * Answers the next element returned by the current NCNode's iterator, and
103      * advances the iterator (to the next NCNode if necessary)
104      */
105     @Override
106     public T next()
107     {
108       if (nodeIterator == null || !nodeIterator.hasNext())
109       {
110         throw new NoSuchElementException();
111       }
112       T result = nodeIterator.next();
113
114       if (!nodeIterator.hasNext())
115       {
116         subrangeIndex = nextSubrange(subrangeIndex);
117       }
118       return result;
119     }
120
121   }
122
123   /*
124    * the number of interval instances represented
125    */
126   private int size;
127
128   /*
129    * a list, in start position order, of sublists of ranges ordered so 
130    * that each contains (or is the same as) the one that follows it
131    */
132   private List<NCNode<T>> subranges;
133
134   /**
135    * Constructor given a list of things that are each located on a contiguous
136    * interval. Note that the constructor may reorder the list.
137    * <p>
138    * We assume here that for each range, start &lt;= end. Behaviour for reverse
139    * ordered ranges is undefined.
140    * 
141    * @param ranges
142    */
143   public NCList(List<T> ranges)
144   {
145     this();
146     build(ranges);
147   }
148
149   /**
150    * Sorts and groups ranges into sublists where each sublist represents an
151    * interval and its contained subintervals
152    * 
153    * @param ranges
154    */
155   protected void build(List<T> ranges)
156   {
157     /*
158      * sort and partition into subranges 
159      * which have no mutual containment
160      */
161     List<IntervalI> sublists = partitionNestedSublists(ranges);
162
163     /*
164      * convert each subrange to an NCNode consisting of a range and
165      * (possibly) its contained NCList
166      */
167     for (IntervalI sublist : sublists)
168     {
169       subranges.add(new NCNode<>(
170               ranges.subList(sublist.getBegin(), sublist.getEnd() + 1)));
171     }
172
173     size = ranges.size();
174   }
175
176   /**
177    * Default constructor
178    */
179   public NCList()
180   {
181     subranges = new ArrayList<>();
182   }
183
184   /**
185    * Sorts and traverses the ranges to identify sublists, whose start intervals
186    * are overlapping or disjoint but not mutually contained. Answers a list of
187    * start-end indices of the sorted list of ranges.
188    * 
189    * @param ranges
190    * @return
191    */
192   protected List<IntervalI> partitionNestedSublists(List<T> ranges)
193   {
194     List<IntervalI> sublists = new ArrayList<>();
195
196     if (ranges.isEmpty())
197     {
198       return sublists;
199     }
200
201     /*
202      * sort by start ascending, length descending, so that
203      * contained intervals follow their containing interval
204      */
205     Collections.sort(ranges, IntervalI.COMPARATOR_BIGENDIAN);
206
207     int listStartIndex = 0;
208
209     IntervalI lastParent = ranges.get(0);
210     boolean first = true;
211
212     for (int i = 0; i < ranges.size(); i++)
213     {
214       IntervalI nextInterval = ranges.get(i);
215       if (!first && !lastParent.properlyContainsInterval(nextInterval))
216       {
217         /*
218          * this interval is not properly contained in the parent; 
219          * close off the last sublist
220          */
221         sublists.add(new Range(listStartIndex, i - 1));
222         listStartIndex = i;
223         lastParent = nextInterval;
224       }
225       first = false;
226     }
227
228     sublists.add(new Range(listStartIndex, ranges.size() - 1));
229     return sublists;
230   }
231
232   /**
233    * Adds one entry to the stored set
234    * 
235    * @param entry
236    */
237   @Override
238   public synchronized boolean add(final T entry)
239   {
240     final NCNode<T> newNode = new NCNode<>(entry);
241     addNode(newNode);
242     return true;
243   }
244
245   /**
246    * Adds one NCNode to this NCList
247    * <p>
248    * This method does not update the <code>size</code> (interval count) of this
249    * NCList, as it may be used to rearrange nodes without changing their count.
250    * Callers should increment the count if needed.
251    * 
252    * @param newNode
253    */
254   protected void addNode(final NCNode<T> newNode)
255   {
256     final long start = newNode.getBegin();
257     final long end = newNode.getEnd();
258     size += newNode.size();
259
260     /*
261      * cases:
262      * 1) precedes all subranges - add as NCNode on front of list
263      * 2) follows all subranges - add as NCNode on end of list
264      * 3) matches a subrange - add as a sibling in the list
265      * 4) properly enclosed by a subrange - add recursively to subrange
266      * 5) properly encloses one or more subranges - push them inside it
267      * 6) spans two subranges - insert between them
268      */
269
270     /*
271      * find the first subrange whose end does not precede entry's start
272      */
273     int candidateIndex = findFirstOverlap(start);
274
275     /*
276      * search for maximal span of subranges i-k that the new entry
277      * encloses; or a subrange that encloses the new entry
278      */
279     boolean enclosing = false;
280     int firstEnclosed = 0;
281     int lastEnclosed = 0;
282
283     for (int j = candidateIndex; j < subranges.size(); j++)
284     {
285       NCNode<T> subrange = subranges.get(j);
286
287       if (subrange.equalsInterval(newNode))
288       {
289         /*
290          * matching interval - insert adjacent
291          */
292         subranges.add(j, newNode);
293         return;
294       }
295
296       if (end < subrange.getBegin() && !enclosing)
297       {
298         /*
299          * new entry lies between subranges j-1 j
300          */
301         subranges.add(j, newNode);
302         return;
303       }
304
305       if (subrange.properlyContainsInterval(newNode))
306       {
307         /*
308          * push new entry inside this subrange as it encloses it
309          */
310         subrange.addNode(newNode);
311         return;
312       }
313
314       if (start <= subrange.getBegin())
315       {
316         if (end >= subrange.getEnd())
317         {
318           /*
319            * new entry encloses this subrange (and possibly preceding ones);
320            * continue to find the maximal list it encloses
321            */
322           if (!enclosing)
323           {
324             firstEnclosed = j;
325           }
326           lastEnclosed = j;
327           enclosing = true;
328           continue;
329         }
330         else
331         {
332           /*
333            * entry spans from before this subrange to inside it
334            */
335           if (enclosing)
336           {
337             /*
338              * entry encloses one or more preceding subranges
339              */
340             push(newNode, firstEnclosed, lastEnclosed);
341           }
342           else
343           {
344             /*
345              * entry overlaps two subranges but doesn't enclose either
346              * so just add it 
347              */
348             subranges.add(j, newNode);
349           }
350           return;
351         }
352       }
353     }
354
355     /*
356      * drops through to here if new range encloses all others
357      * or overlaps the last one
358      */
359     if (enclosing)
360     {
361       push(newNode, firstEnclosed, lastEnclosed);
362     }
363     else
364     {
365       subranges.add(newNode);
366     }
367   }
368
369   @Override
370   public boolean contains(Object entry)
371   {
372     if (!(entry instanceof IntervalI))
373     {
374       return false;
375     }
376     IntervalI interval = (IntervalI) entry;
377
378     /*
379      * find the first sublist that might overlap, i.e. 
380      * the first whose end position is >= from
381      */
382     int candidateIndex = findFirstOverlap(interval.getBegin());
383
384     int to = interval.getEnd();
385
386     for (int i = candidateIndex; i < subranges.size(); i++)
387     {
388       NCNode<T> candidate = subranges.get(i);
389       if (candidate.getBegin() > to)
390       {
391         /*
392          * we are past the end of our target range
393          */
394         break;
395       }
396       if (candidate.contains(interval))
397       {
398         return true;
399       }
400     }
401     return false;
402   }
403
404   /**
405    * Update the tree so that the new node encloses current subranges i to j
406    * (inclusive). That is, replace subranges i-j (inclusive) with a new subrange
407    * that contains them.
408    * 
409    * @param node
410    * @param i
411    * @param j
412    * @throws IllegalArgumentException
413    *           if any of the subranges is not contained by the node's start-end
414    *           range
415    */
416   protected synchronized void push(NCNode<T> node, final int i,
417           final int j)
418   {
419     for (int k = i; k <= j; k++)
420     {
421       NCNode<T> n = subranges.get(k);
422       if (!node.containsInterval(n)) {
423         throw new IllegalArgumentException("Can't push " + n.toString()
424                 + " inside " + node.toString());
425       }
426       node.addNode(n);
427     }
428
429     for (int k = j; k >= i; k--)
430     {
431       subranges.remove(k);
432     }
433     subranges.add(i, node);
434   }
435
436   /**
437    * Answers a list of contained intervals that overlap the given range
438    * 
439    * @param from
440    * @param to
441    * @return
442    */
443   public List<T> findOverlaps(long from, long to)
444   {
445     List<T> result = new ArrayList<>();
446
447     findOverlaps(from, to, result);
448
449     return result;
450   }
451
452   /**
453    * Recursively searches the NCList adding any items that overlap the from-to
454    * range to the result list
455    * 
456    * @param from
457    * @param to
458    * @param result
459    */
460   protected List<T> findOverlaps(long from, long to, List<T> result)
461   {
462
463     // if (OPTION_FIND_ANY)
464     // {
465     // return findAnyOverlaps(from, to, result);
466     // }
467     /*
468      * find the first sublist that might overlap, i.e. 
469      * the first whose end position is >= from
470      */
471     int candidateIndex = findFirstOverlap(from);
472
473     for (int i = candidateIndex; i < subranges.size(); i++)
474     {
475       NCNode<T> candidate = subranges.get(i);
476       if (candidate.getBegin() > to)
477       {
478         /*
479          * we are past the end of our target range
480          */
481         break;
482       }
483       candidate.findOverlaps(from, to, result);
484     }
485     return result;
486   }
487   
488   // /**
489   // * Recursively searches the NCList adding any items that overlap the from-to
490   // * range to the result list
491   // *
492   // * @param from
493   // * @param to
494   // * @param result
495   // */
496   // protected List<T> findAnyOverlaps(long from, long to, List<T> result)
497   // {
498   //
499   // // BH find ANY overlap
500   //
501   // int candidateIndex = findAnyOverlap(subranges, from, to);
502   //
503   // if (candidateIndex < 0)
504   // return result;
505   // for (int i = candidateIndex, n = subranges.size(); i < n; i++)
506   // {
507   // NCNode<T> candidate = subranges.get(i);
508   // if (candidate.getBegin() > to)
509   // {
510   // /*
511   // * we are past the end of our target range
512   // */
513   // break;
514   // }
515   // candidate.findOverlaps(from, to, result);
516   // }
517   //
518   // // BH adds dual-direction check
519   //
520   // for (int i = candidateIndex; --i >= 0;)
521   // {
522   // NCNode<T> candidate = subranges.get(i);
523   // if (candidate.getEnd() < from)
524   // {
525   // break;
526   // }
527   // candidate.findOverlaps(from, to, result);
528   // }
529   // return result;
530   // }
531   //
532   // private int findAnyOverlap(List<NCNode<T>> ranges, long from, long to)
533   // {
534   // int start = 0;
535   // int end = ranges.size() - 1;
536   // while (start <= end)
537   // {
538   // int mid = (start + end) >>> 1;
539   // NCNode<T> r = ranges.get(mid);
540   // if (r.getEnd() >= from)
541   // {
542   // if (r.getBegin() <= to)
543   // return mid;
544   // end = mid - 1;
545   // }
546   // else
547   // {
548   // start = mid + 1;
549   // }
550   // }
551   // return -1;
552   // }
553
554   /**
555    * Search subranges for the first one whose end position is not before the
556    * target range's start position, i.e. the first one that may overlap the
557    * target range. Returns the index in the list of the first such range found,
558    * or the length of the list if none found.
559    * 
560    * @param from
561    * @return
562    */
563   protected int findFirstOverlap(final long from)
564   {
565     return BinarySearcher.findFirst(subranges, (int) from,
566             BinarySearcher.fend);
567   }
568
569   /**
570    * Formats the tree as a bracketed list e.g.
571    * 
572    * <pre>
573    * [1-100 [10-30 [10-20]], 15-30 [20-20]]
574    * </pre>
575    */
576   @Override
577   public String toString()
578   {
579     return subranges.toString();
580   }
581
582   /**
583    * Answers the NCList as an indented list
584    * 
585    * @return
586    */
587   public String prettyPrint()
588   {
589     StringBuilder sb = new StringBuilder(512);
590     int offset = 0;
591     int indent = 2;
592     prettyPrint(sb, offset, indent);
593     sb.append('\n');// System.lineSeparator());
594     return sb.toString();
595   }
596
597   /**
598    * @param sb
599    * @param offset
600    * @param indent
601    */
602   void prettyPrint(StringBuilder sb, int offset, int indent)
603   {
604     boolean first = true;
605     for (NCNode<T> subrange : subranges)
606     {
607       if (!first)
608       {
609         sb.append('\n');// System.lineSeparator());
610       }
611       first = false;
612       subrange.prettyPrint(sb, offset, indent);
613     }
614   }
615
616   /**
617    * Answers true if the store's structure is valid (nesting containment rules
618    * are obeyed), else false. For use in testing and debugging.
619    * 
620    * @return
621    */
622   public boolean isValid()
623   {
624     int count = 0;
625     for (NCNode<T> subrange : subranges)
626     {
627       count += subrange.size();
628     }
629     if (count != size)
630     {
631       return false;
632     }
633     return isValid(Integer.MIN_VALUE, Integer.MAX_VALUE);
634   }
635
636   /**
637    * Answers true if the data held satisfy the rules of construction of an
638    * NCList bounded within the given start-end range, else false.
639    * <p>
640    * Each subrange must lie within start-end (inclusive). Subranges must be
641    * ordered by start position ascending, and within that by end position
642    * descending.
643    * <p>
644    * 
645    * @param start
646    * @param end
647    * @return
648    */
649   boolean isValid(final int start, final int end)
650   {
651     NCNode<T> lastRange = null;
652
653     for (NCNode<T> subrange : subranges)
654     {
655       if (subrange.getBegin() < start)
656       {
657         System.err.println("error in NCList: range " + subrange.toString()
658                 + " starts before " + end);
659         return false;
660       }
661       if (subrange.getEnd() > end)
662       {
663         System.err.println("error in NCList: range " + subrange.toString()
664                 + " ends after " + end);
665         return false;
666       }
667
668       if (lastRange != null)
669       {
670         if (subrange.getBegin() < lastRange.getBegin())
671         {
672           System.err.println("error in NCList: range " + subrange.toString()
673                   + " starts before " + lastRange.toString());
674           return false;
675         }
676         if (subrange.properlyContainsInterval(lastRange))
677         {
678           System.err.println("error in NCList: range " + subrange.toString()
679                   + " encloses preceding: " + lastRange.toString());
680           return false;
681         }
682         if (lastRange.properlyContainsInterval(subrange))
683         {
684           System.err.println("error in NCList: range " + subrange.toString()
685                   + " enclosed by preceding: " + lastRange.toString());
686           return false;
687         }
688       }
689       lastRange = subrange;
690
691       if (!subrange.isValid())
692       {
693         return false;
694       }
695     }
696     return true;
697   }
698
699   /**
700    * Answers the number of intervals stored
701    * 
702    * @return
703    */
704   @Override
705   public int size()
706   {
707     return size;
708   }
709
710   /**
711    * Answers a list of all entries stored, in no guaranteed order. This method
712    * is not synchronized, so is not thread-safe.
713    */
714   public List<T> getEntries()
715   {
716     List<T> result = new ArrayList<>();
717     getEntries(result);
718     return result;
719   }
720
721   /**
722    * Adds all contained entries to the given list
723    * 
724    * @param result
725    */
726   void getEntries(List<T> result)
727   {
728     for (NCNode<T> subrange : subranges)
729     {
730       subrange.getEntries(result);
731     }
732   }
733
734   /**
735    * Removes the first interval <code>I</code>found that is equal to T
736    * (<code>I.equals(T)</code>). Answers true if an interval is removed, false
737    * if no match is found. This method is synchronized so thread-safe.
738    * 
739    * @param entry
740    * @return
741    */
742   public synchronized boolean remove(T entry)
743   {
744     if (entry == null)
745     {
746       return false;
747     }
748     int i = findFirstOverlap(entry.getBegin());
749
750     for (; i < subranges.size(); i++)
751     {
752       NCNode<T> subrange = subranges.get(i);
753       if (subrange.getBegin() > entry.getBegin())
754       {
755         /*
756          * not found
757          */
758         return false;
759       }
760       NCList<T> subRegions = subrange.getSubRegions();
761
762       if (subrange.getRegion().equals(entry))
763       {
764         /*
765          * if the subrange is rooted on this entry, remove it,
766          * and remove and promote its subregions (if any)  
767          */
768         subranges.remove(i);
769         size -= subrange.size();
770         if (subRegions != null)
771         {
772           for (NCNode<T> r : subRegions.subranges)
773           {
774             addNode(r);
775           }
776         }
777         return true;
778       }
779       else
780       {
781         if (subrange.remove(entry))
782         {
783           size--;
784           return true;
785         }
786       }
787     }
788     return false;
789   }
790
791   /**
792    * Answers the depth of interval nesting of this object, where 1 means there
793    * are no nested sub-intervals
794    * 
795    * @return
796    */
797   public int getDepth()
798   {
799     int subDepth = 0;
800     for (NCNode<T> subrange : subranges)
801     {
802       subDepth = Math.max(subDepth, subrange.getDepth());
803     }
804
805     return subDepth;
806   }
807
808   /**
809    * Answers an iterator over the contained intervals, with no particular order
810    * guaranteed. The iterator does not support the optional <code>remove</code>
811    * operation (throws <code>UnsupportedOperationException</code> if attempted).
812    */
813   @Override
814   public Iterator<T> iterator()
815   {
816     return new NCListIterator();
817   }
818
819   @Override
820   public synchronized void clear()
821   {
822     subranges.clear();
823     size = 0;
824   }
825
826   public int getWidth()
827   {
828     return subranges.size();
829   }
830 }