b010340bbcd09d7cd2e17b95636af66015940847
[jalview.git] / src / intervalstore / nonc / IntervalStore.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.nonc;
33
34 import jalview.datamodel.SequenceFeature;
35
36 import java.util.AbstractCollection;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.BitSet;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.NoSuchElementException;
45
46 import intervalstore.api.IntervalI;
47 import intervalstore.api.IntervalStoreI;
48
49 /**
50  * 
51  * A fourth idea, implementing NCList as a pointer system identical in operation
52  * to IntervalStoreJ's implementation using ArrayLists but here using just two
53  * int[] arrays and a single IntervalI[] array that is in the proper order for
54  * holding all nested and unnested arrays.
55  * 
56  * Use of unnesting is optional and can be experimented with by changing the
57  * createUnnested flag to false.
58  * 
59  * Preliminary testing suggests that this implementation is about 10% faster for
60  * store interval size 50, store sequence factor 10, query width -1000 (fixed
61  * 1000-unit-wide window), and query count 100000.
62  * 
63  * Origional note (Mungo Carstairs, IntervalStoreJ)
64  * 
65  * A Collection class to store interval-associated data, with options for "lazy"
66  * sorting so as to speed incremental construction of the data prior to issuing
67  * a findOverlap call.
68  * 
69  * Accepts duplicate entries but not null values.
70  * 
71  * 
72  * 
73  * @author Bob Hanson 2019.09.01
74  *
75  * @param <T>
76  *          any type providing <code>getBegin()</code> and
77  *          <code>getEnd()</code>, primarily
78  */
79 public class IntervalStore<T extends IntervalI>
80         extends AbstractCollection<T> implements IntervalStoreI<T>
81 {
82
83   /**
84    * Search for the last interval that ends at or just after the specified
85    * position. In the situation that there are multiple intervals starting at
86    * pos, this method returns the first of those.
87    * 
88    * @param nests
89    *          the nest-ordered array from createArrays()
90    * @param from
91    *          the position at the start of the interval of interest
92    * @param start
93    *          the starting point for the subarray search
94    * @param end
95    *          the ending point for the subarray search
96    * @return index into the nests array or one greater than end if not found
97    */
98   public static int binarySearchFirstEndNotBefore(IntervalI[] nests, long from,
99           int start, int end)
100   {
101     int matched = end + 1;
102     int mid;
103     while (start <= end)
104     {
105       mid = (start + end) >>> 1;
106       if (nests[mid].getEnd() >= from)
107       {
108         matched = mid;
109         end = mid - 1;
110       }
111       else
112       {
113         start = mid + 1;
114       }
115     }
116     return matched;
117   }
118
119   /**
120    * My preference is for a bigendian comparison, but you may differ.
121    */
122   private Comparator<? super IntervalI> icompare;
123
124   /**
125    * bigendian is what NCList does; change icompare to switch to that
126    */
127   private boolean bigendian;
128
129   // private final boolean DO_PRESORT;
130   //
131   // private boolean isSorted;
132   //
133   private boolean createUnnested = true;
134
135   private int minStart = Integer.MAX_VALUE, maxStart = Integer.MIN_VALUE,
136           maxEnd = Integer.MAX_VALUE;
137
138   private boolean isTainted;
139
140   private int capacity = 8;
141
142   protected IntervalI[] intervals = new IntervalI[capacity];
143
144   private int[] offsets;
145
146   protected int intervalCount;
147
148   private int added;
149
150   private int deleted;
151
152   private BitSet bsDeleted;
153
154   /**
155    * the key array that lists the intervals in sub-interval order so that the
156    * binary search can be isolated to a single subinterval just by indicating
157    * start and end within one array
158    */
159   private IntervalI[] nests;
160
161   /**
162    * pointers to the starting positions in nests[] for a subinterval; the first
163    * element is the "unnested" pointer when unnesting (2) or the root level nest
164    * pointer when not unnesting (1); the second element is root level nest when
165    * unnesting or the start of nest data when not unnesting; after that, nests
166    * are in contiguous sets of binary-searchable blocks
167    * 
168    */
169   private int[] nestOffsets;
170
171   /**
172    * the count of intervals within a nest
173    * 
174    */
175   private int[] nestLengths;
176
177   /**
178    * pointer to the root nest (intervalCount)
179    */
180   private int root;
181
182   /**
183    * pointer to the unnested set (intervalCount + 1);
184    */
185   private int unnested;
186
187   public IntervalStore()
188   {
189     this(null);
190   }
191
192   // public IntervalStore(boolean presort)
193   // {
194   // this(null, presort);
195   // }
196   //
197   /**
198    * Constructor given a list of intervals. Note that the list may get sorted as
199    * a side-effect of calling this constructor.
200    */
201   public IntervalStore(List<T> intervals)
202   {
203     this(intervals, null, true);
204   }
205
206   /**
207    * 
208    * @param intervals
209    *          intervals to initialize with (others may still be added)
210    * @param comparator
211    *          IntervalI.COMPARATOR_BEGIN_ASC_END_ASC or
212    *          IntervalI.COMPARE_BEGIN_ASC_END_DESC, but this could also be one
213    *          that sorts by description as well, for example.
214    * @param bigendian
215    *          true if the comparator sorts [10-100] before [10-80]; defaults to
216    *          true
217    */
218   public IntervalStore(List<T> intervals,
219           Comparator<? super IntervalI> comparator, boolean bigendian)
220   {
221     // COMPARE_BEGIN_ASC_END_DESC is standard, meaning
222     // the order will be [10,100] before [10,80]
223     // this order doesn't really matter much.
224     icompare = (comparator != null ? comparator
225             : bigendian ? IntervalI.COMPARE_BEGIN_ASC_END_DESC
226                     : IntervalI.COMPARE_BEGIN_ASC_END_ASC);
227     this.bigendian = bigendian;
228
229     if (intervals != null)
230     {
231       // So, five hours later, we learn that all my timing has been thrown off
232       // because I used Array.sort, which if you look in the Java JDK is exactly
233       // what Collections.sort is, but for whatever reason, all my times were
234       // high by about 100-200 ms 100% reproducibly. Just one call to Array.sort
235       // prior to the nanotimer start messed it all up. Some sort of memory or
236       // garbage issue; I do not know. But using Collections.sort here fixes the
237       // problem.
238
239       Collections.sort(intervals, icompare);
240       intervals.toArray(
241               this.intervals = new IntervalI[capacity = intervalCount = intervals
242                       .size()]);
243     }
244     isTainted = true;
245     // DO_PRESORT = presort;
246     if (// DO_PRESORT &&
247     intervalCount > 1)
248     {
249       updateMinMaxStart();
250       // isSorted = true;
251       ensureFinalized();
252     }
253     // else
254     // {
255     // isSorted = DO_PRESORT;
256     // isTainted = true;
257     // }
258   }
259
260   /**
261    * Adds one interval to the store, allowing duplicates.
262    * 
263    * @param interval
264    */
265   @Override
266   public boolean add(T interval)
267   {
268     return add(interval, true);
269   }
270
271   /**
272    * Adds one interval to the store, optionally checking for duplicates.
273    * 
274    * This fast-adding algorithm uses a double-length int[] (offsets) to hold
275    * pointers into intervals[] that allows continual sorting of an expanding
276    * array buffer. When the time comes, this is cleaned up and packed back into
277    * a standard array, but in the mean time, it can be added to with no loss of
278    * sorting.
279    * 
280    * @param interval
281    * @param allowDuplicates
282    */
283   @Override
284   public boolean add(T interval, boolean allowDuplicates)
285   {
286     if (interval == null)
287     {
288       return false;
289     }
290
291     if (deleted > 0)
292     {
293       finalizeDeletion();
294     }
295     if (!isTainted)
296     {
297       offsets = null;
298       isTainted = true;
299     }
300
301     synchronized (intervals)
302     {
303       int index = intervalCount;
304       int start = interval.getBegin();
305
306       if (intervalCount + added + 1 >= capacity)
307       {
308         intervals = finalizeAddition(
309                 new IntervalI[capacity = capacity << 1]);
310
311       }
312
313       // if (DO_PRESORT && isSorted)
314       // {
315         if (intervalCount == 0)
316         {
317           // ignore
318         }
319         else
320         {
321         index = findInterval(interval, allowDuplicates);
322           if (!allowDuplicates && index >= 0)
323           {
324             return false;
325           }
326           if (index < 0)
327           {
328             index = -1 - index;
329           }
330           else
331           {
332             index++;
333           }
334         }
335
336       // }
337       // else
338       // {
339       // if (!allowDuplicates && findInterval(interval) >= 0)
340       // {
341       // return false;
342       // }
343       // isSorted = false;
344       // }
345
346       if (index == intervalCount)
347       {
348         intervals[intervalCount++] = interval;
349         // System.out.println("added " + intervalCount + " " + interval);
350       }
351       else
352       {
353         int pt = capacity - ++added;
354         intervals[pt] = interval;
355         // System.out.println("stashed " + pt + " " + interval + " for "
356         // + index + " " + intervals[index]);
357         if (offsets == null)
358         {
359           offsets = new int[capacity];
360         }
361
362         offsets[pt] = offsets[index];
363
364         offsets[index] = pt;
365       }
366
367       minStart = Math.min(minStart, start);
368       maxStart = Math.max(maxStart, start);
369       return true;
370     }
371   }
372
373   /**
374    * Clean up the intervals array into a simple ordered array.
375    * 
376    * @param dest
377    * @return
378    */
379   private IntervalI[] finalizeAddition(IntervalI[] dest)
380   {
381     if (dest == null)
382     {
383       dest = intervals;
384     }
385     if (added == 0)
386     {
387       if (intervalCount > 0 && dest != intervals)
388       {
389         System.arraycopy(intervals, 0, dest, 0, intervalCount);
390       }
391       capacity = dest.length;
392       return dest;
393     }
394     // System.out.println("finalizing " + intervalCount + " " + added);
395
396     // array is [(intervalCount)...null...(added)]
397
398     int ntotal = intervalCount + added;
399     for (int ptShift = ntotal, pt = intervalCount; pt >= 0;)
400     {
401       int pt0 = pt;
402       while (--pt >= 0 && offsets[pt] == 0)
403       {
404         
405       }
406       if (pt < 0)
407       {
408         pt = 0;
409       }
410       int nOK = pt0 - pt;
411       // shift upper intervals right
412       ptShift -= nOK;
413       if (nOK > 0)
414       {
415         System.arraycopy(intervals, pt, dest, ptShift, nOK);
416       }
417       if (added == 0)
418       {
419         break;
420       }
421       for (int offset = offsets[pt]; offset > 0; offset = offsets[offset])
422       {
423         dest[--ptShift] = intervals[offset];
424         --added;
425       }
426     }
427     offsets = null;
428     intervalCount = ntotal;
429     capacity = dest.length;
430     return dest;
431   }
432
433   /**
434    * A binary search for a duplicate.
435    * 
436    * @param interval
437    * @return
438    */
439   public int binaryIdentitySearch(IntervalI interval)
440   {
441     return binaryIdentitySearch(interval, null, false);
442   }
443
444   /**
445    * for remove() and contains()
446    * 
447    * @param list
448    * @param interval
449    * @param bsIgnore
450    *          for deleted
451    * @param rangeOnly
452    *          don't do a full identity check, just a range check
453    * @return index or, if not found, -1 - "would be here"
454    */
455   private int binaryIdentitySearch(IntervalI interval, BitSet bsIgnore,
456           boolean rangeOnly)
457   {
458     int start = 0;
459     int r0 = interval.getBegin();
460     int r1 = interval.getEnd();
461     int end = intervalCount - 1;
462     if (end < 0 || r0 < minStart)
463     {
464       return -1;
465     }
466     if (r0 > maxStart)
467     {
468       return -1 - intervalCount;
469     }
470     while (start <= end)
471     {
472       int mid = (start + end) >>> 1;
473       IntervalI r = intervals[mid];
474       switch (compareRange(r, r0, r1))
475       {
476       case -1:
477         start = mid + 1;
478         continue;
479       case 1:
480         end = mid - 1;
481         continue;
482       case 0:
483         IntervalI iv = intervals[mid];
484         if (!rangeOnly && (bsIgnore == null || !bsIgnore.get(mid))
485                 && sameInterval(interval, iv))
486         {
487           return mid;
488           // found one; just scan up and down now, first checking the range, but
489           // also checking other possible aspects of equivalence.
490         }
491
492         for (int i = mid; ++i <= end;)
493         {
494           if ((iv = intervals[i]).getBegin() != r0 || iv.getEnd() != r1)
495           {
496             break;
497           }
498           if (!rangeOnly && (bsIgnore == null || !bsIgnore.get(i))
499                   && sameInterval(interval, iv))
500           {
501             return i;
502           }
503         }
504         for (int i = mid; --i >= start;)
505         {
506           if ((iv = intervals[i]).getBegin() != r0
507                   || (bigendian ? r1 < iv.getEnd() : iv.getEnd() < r1))
508           {
509             return -1 - ++i;
510           }
511           if ((bsIgnore == null || !bsIgnore.get(i))
512                   && sameInterval(interval, iv))
513           {
514             return i;
515           }
516         }
517         return -1 - mid;
518       }
519     }
520     return -1 - start;
521   }
522
523   public boolean canCheckForDuplicates()
524   {
525     return true;
526   }
527
528   /**
529    * Reset all arrays.
530    * 
531    */
532   @Override
533   public void clear()
534   {
535     intervalCount = added = 0;
536     // isSorted = true;
537     isTainted = true;
538     offsets = null;
539     intervals = new IntervalI[8];
540     nestOffsets = nestLengths = null;
541     nests = null;
542     minStart = maxEnd = Integer.MAX_VALUE;
543     maxStart = Integer.MIN_VALUE;
544   }
545
546   /**
547    * Compare an interval t to a from/to range for insertion purposes
548    * 
549    * @param t
550    * @param from
551    * @param to
552    * @return 0 if same, 1 if start is after from, or start equals from and
553    *         [bigendian: end is before to | littleendian: end is after to], else
554    *         -1
555    */
556   private int compareRange(IntervalI t, long from, long to)
557   {
558     int order = Long.signum(t.getBegin() - from);
559     return (order == 0
560             ? Long.signum(bigendian ? to - t.getEnd() : t.getEnd() - to)
561             : order);
562   }
563
564   @Override
565   public boolean contains(Object entry)
566   {
567     if (entry == null || intervalCount == 0 && added == 0 && deleted == 0)
568     {
569       return false;
570     }
571     if (// !isSorted ||
572     deleted > 0)
573     {
574       sort();
575     }
576     return (findInterval((IntervalI) entry, false) >= 0);
577   }
578
579   /**
580    * Check to see if a given interval is within another.
581    * 
582    * Not implemented.
583    * 
584    * @param outer
585    * @param inner
586    * @return
587    */
588   public boolean containsInterval(IntervalI outer, IntervalI inner)
589   {
590     return false; // not applicable
591   }
592
593   /**
594    * Ensure that all addition, deletion, and sorting has been done, and that the
595    * nesting arrays have been created so that we are ready for findOverlaps().
596    * 
597    */
598
599   private void ensureFinalized()
600   {
601     if (isTainted)
602     {
603       if (// !isSorted ||
604       added > 0 || deleted > 0)
605       {
606         sort();
607       }
608       if (intervalCount > 0)
609       {
610         createArrays();
611       }
612       isTainted = false;
613     }
614   }
615
616   /**
617    * Find all overlaps within the given range, inclusively.
618    * 
619    * @return a list sorted in ascending order of start position
620    * 
621    */
622   @Override
623   public List<T> findOverlaps(long from, long to)
624   {
625     return findOverlaps(from, to, null);
626   }
627
628   /**
629    * Find all overlaps within the given range, inclusively.
630    * 
631    * @return a list sorted in the order provided by the features list comparator
632    * 
633    */
634
635   @SuppressWarnings("unchecked")
636   @Override
637   public List<T> findOverlaps(long from, long to, List<T> result)
638   {
639     if (result == null)
640     {
641       result = new ArrayList<>();
642     }
643     switch (intervalCount + added)
644     {
645     case 0:
646       return result;
647     case 1:
648       IntervalI sf = intervals[0];
649       if (sf.getBegin() <= to && sf.getEnd() >= from)
650       {
651         result.add((T) sf);
652       }
653       return result;
654     }
655
656     ensureFinalized();
657
658     if (from > maxEnd || to < minStart)
659     {
660       return result;
661     }
662     if (createUnnested)
663     {
664       if (nestLengths[unnested] > 0)
665       {
666         searchUnnested(from, to, (List<IntervalI>) result);
667       }
668     }
669     if (nestLengths[root] > 0)
670     {
671       search(nests, from, to, root, result);
672     }
673     return result;
674   }
675
676   /**
677    * A simpler search, since we know we don't have any subintervals. Not
678    * necessary, actually.
679    * 
680    * @param nestOffsets
681    * @param nestLengths
682    * @param nests
683    * @param from
684    * @param to
685    * @param result
686    */
687   private void searchUnnested(long from, long to, List<IntervalI> result)
688   {
689     int start = nestOffsets[unnested];
690     int end = start + nestLengths[unnested] - 1;
691     for (int pt = binarySearchFirstEndNotBefore(nests, from, start,
692             end); pt <= end; pt++)
693     {
694       IntervalI ival = nests[pt];
695       if (ival.getBegin() > to)
696       {
697         break;
698       }
699       result.add(ival);
700     }
701   }
702
703   /**
704    * The main search of the nests[] array's subarrays
705    * 
706    * @param nests
707    * @param from
708    * @param to
709    * @param nest
710    * @param result
711    */
712   @SuppressWarnings("unchecked")
713   private void search(IntervalI[] nests, long from, long to, int nest,
714           List<T> result)
715   {
716     int start = nestOffsets[nest];
717     int n = nestLengths[nest];
718     int end = start + n - 1;
719     IntervalI first = nests[start];
720     IntervalI last = nests[end];
721
722     // quick tests for common cases:
723     // out of range
724     if (last.getEnd() < from || first.getBegin() > to)
725     {
726       return;
727     }
728     int pt;
729     switch (n)
730     {
731     case 1:
732       // just one interval and hasn't failed begin/end test
733       pt = start;
734       break;
735     case 2:
736       // just two and didn't fail begin/end test
737       // so there is only one option: either the first or the second is our
738       // winner
739       pt = (first.getEnd() >= from ? start : end);
740       break;
741     default:
742       // do the binary search
743       pt = binarySearchFirstEndNotBefore(nests, from, start, end);
744       break;
745     }
746     for (; pt <= end; pt++)
747     {
748       IntervalI ival = nests[pt];
749       // check for out of range
750       if (ival.getBegin() > to)
751       {
752         break;
753       }
754       result.add((T) ival);
755       if (nestLengths[pt] > 0)
756       {
757         // check subintervals in this nest
758         search(nests, from, to, pt, result);
759       }
760     }
761   }
762
763   /**
764    * return the i-th interval in the designated order (bigendian or
765    * littleendian)
766    */
767   public IntervalI get(int i)
768   {
769     if (i < 0 || i >= intervalCount + added)
770     {
771       return null;
772     }
773     ensureFinalized();
774     return intervals[i];
775   }
776
777   /**
778    * Return the deepest level of nesting.
779    * 
780    */
781   @Override
782   public int getDepth()
783   {
784     ensureFinalized();
785     BitSet bsTested = new BitSet();
786     return Math.max((createUnnested ? getDepth(unnested, bsTested) : 0),
787             getDepth(root, bsTested));
788   }
789
790   /**
791    * Iteratively dive deeply.
792    * 
793    * @param pt
794    * @param bsTested
795    * @return
796    */
797   private int getDepth(int pt, BitSet bsTested)
798   {
799     int maxDepth = 0;
800     int depth;
801     int n = nestLengths[pt];
802     if (n == 0 || bsTested.get(pt))
803     {
804       return 1;
805     }
806     bsTested.set(pt);
807     for (int st = nestOffsets[pt], i = st + n; --i >= st;)
808     {
809       if ((depth = getDepth(i, bsTested)) > maxDepth)
810       {
811         maxDepth = depth;
812       }
813     }
814     return maxDepth + 1;
815   }
816
817   /**
818    * Get the number of root-level nests.
819    * 
820    */
821   public int getWidth()
822   {
823     ensureFinalized();
824     return nestLengths[root] + (createUnnested ? nestLengths[unnested] : 0);
825   }
826
827   public boolean isValid()
828   {
829     ensureFinalized();
830     return true;
831   }
832
833   /**
834    * Answers an iterator over the intervals in the store, with no particular
835    * ordering guaranteed. The iterator does not support the optional
836    * <code>remove</code> operation (throws
837    * <code>UnsupportedOperationException</code> if attempted).
838    */
839   @Override
840   public Iterator<T> iterator()
841   {
842     ensureFinalized();
843     return new Iterator<T>()
844     {
845
846       private int next;
847
848       @Override
849       public boolean hasNext()
850       {
851         return next < intervalCount;
852       }
853
854       @SuppressWarnings("unchecked")
855       @Override
856       public T next()
857       {
858         if (next >= intervalCount)
859         {
860           throw new NoSuchElementException();
861         }
862         return (T) intervals[next++];
863       }
864
865     };
866   }
867
868   /**
869    * Indented printing of the intervals.
870    * 
871    */
872   @Override
873   public String prettyPrint()
874   {
875     ensureFinalized();
876     StringBuffer sb = new StringBuffer();
877     if (createUnnested)
878     {
879       sb.append("unnested:");
880       dump(intervalCount + 1, sb, "\n");
881       sb.append("\nnested:");
882     }
883     dump(intervalCount, sb, "\n");
884     return sb.toString();
885   }
886
887   /**
888    * Iterative nest dump.
889    * 
890    * @param nest
891    * @param sb
892    * @param sep
893    */
894   private void dump(int nest, StringBuffer sb, String sep)
895   {
896     int pt = nestOffsets[nest];
897     int n = nestLengths[nest];
898     sep += "  ";
899
900     for (int i = 0; i < n; i++)
901     {
902       sb.append(sep).append(nests[pt + i].toString());
903       if (nestLengths[pt + i] > 0)
904       {
905         dump(pt + i, sb, sep + "  ");
906       }
907     }
908   }
909
910   @Override
911   public synchronized boolean remove(Object o)
912   {
913     // if (o == null)
914     // {
915     // throw new NullPointerException();
916     // }
917     return (o != null && intervalCount > 0
918             && removeInterval((IntervalI) o));
919   }
920
921   /**
922    * Find the interval or return where it should go, possibly into the add
923    * buffer
924    * 
925    * @param interval
926    * @param rangeOnly
927    *          don't do a full identity check, just a range check
928    * 
929    * @return index (nonnegative) or index where it would go (negative)
930    */
931
932   private int findInterval(IntervalI interval, boolean rangeOnly)
933   {
934
935     // if (isSorted)
936     // {
937     int pt = binaryIdentitySearch(interval, null, rangeOnly);
938       // if (addPt == intervalCount || offsets[pt] == 0)
939       // return pt;
940       if (pt >= 0 || added == 0 || pt == -1 - intervalCount)
941       {
942         return pt;
943       }
944       pt = -1 - pt;
945       int start = interval.getBegin();
946       int end = interval.getEnd();
947
948       int match = pt;
949
950       while ((pt = offsets[pt]) != 0)
951       {
952         IntervalI iv = intervals[pt];
953         switch (compareRange(iv, start, end))
954         {
955         case -1:
956           break;
957         case 0:
958         if (!rangeOnly && sameInterval(iv, interval))
959           {
960             return pt;
961           }
962           // fall through
963         case 1:
964           match = pt;
965           continue;
966         }
967       }
968       return -1 - match;
969     // }
970     // else
971     // {
972     // int i = intervalCount;
973     // while (--i >= 0 && !sameInterval(intervals[i], interval))
974     // {
975     // ;
976     // }
977     // return i;
978     // }
979   }
980
981   /**
982    * Uses a binary search to find the entry and removes it if found.
983    * 
984    * @param interval
985    * @return
986    */
987   protected boolean removeInterval(IntervalI interval)
988   {
989
990     if (// !isSorted ||
991     added > 0)
992     {
993       sort();
994     }
995     int i = binaryIdentitySearch(interval, bsDeleted, false);
996     if (i < 0)
997     {
998       return false;
999     }
1000     if (deleted == 0)
1001     {
1002       if (bsDeleted == null)
1003       {
1004         bsDeleted = new BitSet(intervalCount);
1005       }
1006       else
1007       {
1008         bsDeleted.clear();
1009       }
1010     }
1011     bsDeleted.set(i);
1012     deleted++;
1013     return (isTainted = true);
1014   }
1015
1016   /**
1017    * Fill in the gaps of the intervals array after one or more deletions.
1018    * 
1019    */
1020   private void finalizeDeletion()
1021   {
1022     if (deleted == 0)
1023     {
1024       return;
1025     }
1026
1027     // ......xxx.....xxxx.....xxxxx....
1028     // ......^i,pt
1029     // ...... .......
1030     // ............
1031     for (int i = bsDeleted.nextSetBit(0), pt = i; i >= 0;)
1032     {
1033       i = bsDeleted.nextClearBit(i + 1);
1034       int pt1 = bsDeleted.nextSetBit(i + 1);
1035       if (pt1 < 0)
1036       {
1037         pt1 = intervalCount;
1038       }
1039       int n = pt1 - i;
1040       System.arraycopy(intervals, i, intervals, pt, n);
1041       pt += n;
1042       if (pt1 == intervalCount)
1043       {
1044         for (i = pt1; --i >= pt;)
1045         {
1046           intervals[i] = null;
1047         }
1048         intervalCount -= deleted;
1049         deleted = 0;
1050         bsDeleted.clear();
1051         break;
1052       }
1053       i = pt1;
1054     }
1055
1056   }
1057
1058   /**
1059    * Recreate the key nest arrays.
1060    * 
1061    */
1062   public boolean revalidate()
1063   {
1064     isTainted = true;
1065     // isSorted = false;
1066     ensureFinalized();
1067     return true;
1068   }
1069
1070   /**
1071    * Return the total number of intervals in the store.
1072    * 
1073    */
1074   @Override
1075   public int size()
1076   {
1077     return intervalCount + added - deleted;
1078   }
1079
1080   /**
1081    * AbstractCollection override to ensure that we have finalized the store.
1082    */
1083   @Override
1084   public Object[] toArray()
1085   {
1086     ensureFinalized();
1087     return super.toArray();
1088   }
1089
1090   /**
1091    * Sort intervals by start.
1092    */
1093   private void sort()
1094   {
1095     if (added > 0)
1096     {
1097       intervals = finalizeAddition(new IntervalI[intervalCount + added]);
1098     }
1099     else if (deleted > 0)
1100     {
1101       finalizeDeletion();
1102     }
1103     else
1104     {
1105       // SOMETHING HAPPENS WHEN Arrays.sort is run that
1106       // adds 100 ms to a 150 ms run time.
1107       // I don't know why.
1108       Arrays.sort(intervals, 0, intervalCount, icompare);
1109     }
1110     updateMinMaxStart();
1111     // isSorted = true;
1112   }
1113
1114   /**
1115    * Create the key arrays: nests, nestOffsets, and nestLengths. The starting
1116    * point is getting the container array, which may point to then root nest or
1117    * the unnested set.
1118    * 
1119    * This is a pretty complicated method; it was way simpler before I decided to
1120    * support unnesting as an option.
1121    *
1122    */
1123   private void createArrays()
1124   {
1125
1126     // goal here is to create an array, nests[], that is a block
1127
1128     /**
1129      * When unnesting, we need a second top-level listing.
1130      * 
1131      */
1132     int len = intervalCount + (createUnnested ? 2 : 1);
1133     root = intervalCount;
1134     unnested = intervalCount + 1;
1135
1136     /**
1137      * The three key arrays produced by this method:
1138      */
1139     nests = new IntervalI[intervalCount];
1140     nestOffsets = new int[len];
1141     nestLengths = new int[len];
1142
1143     /**
1144      * the objectives of Phase One
1145      */
1146
1147     int[] myContainer = new int[intervalCount];
1148     int[] counts = new int[len];
1149
1150     // Phase One: Get the temporary container array myContainer.
1151
1152     myContainer[0] = (createUnnested ? unnested : root);
1153     counts[myContainer[0]] = 1;
1154
1155     // memories for previous begin and end
1156
1157     int beginLast = intervals[0].getBegin();
1158     int endLast = intervals[0].getEnd();
1159
1160     // memories for previous unnested pt, begin, and end
1161
1162     int ptLastNot2 = root;
1163     int beginLast2 = beginLast;
1164     int endLast2 = endLast;
1165
1166     for (int i = 1; i < intervalCount; i++)
1167     {
1168       int pt = i - 1;
1169       int begin = intervals[i].getBegin();
1170       int end = intervals[i].getEnd();
1171
1172       // initialize the container to either root or unnested
1173
1174       myContainer[i] = myContainer[0];
1175
1176       // OK, now figure it all out...
1177
1178       boolean isNested;
1179       if (createUnnested)
1180       {
1181         // Using a method isNested(...) here, because there are different
1182         // ways of defining "nested" when start or end are the
1183         // same. The definition used here would not be my first choice,
1184         // but it matches results for IntervalStoreJ
1185         // perfectly, down to the exact number of times that the
1186         // binary search runs through its start/mid/end loops in findOverlap.
1187
1188         // beginLast2 and endLast2 refer to the last unnested level
1189
1190         isNested = isNested(begin, end, beginLast2, endLast2);
1191         if (isNested)
1192         {
1193           // this is tricky; making sure we properly get the
1194           // nests that are to be removed from the top-level
1195           // unnested list assigned a container root, while all
1196           // top-level nests get the pointer to unnested.
1197
1198           pt = ptLastNot2;
1199           isNested = (pt == root || isNested(begin, end,
1200                   intervals[pt].getBegin(), intervals[pt].getEnd()));
1201           if (!isNested)
1202           {
1203             myContainer[i] = root;
1204           }
1205         }
1206       }
1207       else
1208       {
1209         isNested = isNested(begin, end, beginLast, endLast);
1210       }
1211
1212       // ...almost done...
1213
1214       if (isNested)
1215       {
1216         myContainer[i] = pt;
1217       }
1218       else
1219       {
1220
1221         // monotonic -- find the parent that is doing the nesting
1222
1223         while ((pt = myContainer[pt]) < root)
1224         {
1225           if (isNested(begin, end, intervals[pt].getBegin(),
1226                   intervals[pt].getEnd()))
1227           {
1228             myContainer[i] = pt;
1229             // fully contained by a previous interval
1230             // System.out.println("mycontainer " + i + " = " + pt);
1231             break;
1232           }
1233         }
1234       }
1235
1236       // update the counts and pointers
1237
1238       counts[myContainer[i]]++;
1239       if (myContainer[i] == unnested)
1240       {
1241         endLast2 = end;
1242         beginLast2 = begin;
1243       }
1244       else
1245       {
1246         ptLastNot2 = i;
1247         endLast = end;
1248         beginLast = begin;
1249       }
1250     }
1251
1252     // System.out.println("intervals " + Arrays.toString(intervals));
1253     // System.out.println("conts " + Arrays.toString(myContainer));
1254     // System.out.println("counts " + Arrays.toString(counts));
1255
1256     // Phase Two: Construct the nests[] array and its associated
1257     // starting pointer array and nest element counts. These counts
1258     // are actually produced above, but we reconstruct it as a set
1259     // of dynamic pointers during construction.
1260     
1261     // The reason this is two phases is that only now do we know where to start
1262     // the nested set. If we did not unnest, we could do this all in one pass
1263     // using a reverse sort for the root, and then just reverse that, but with
1264     // unnesting, we have two unknown starts, and that doesn't give us that
1265     // opportunity.
1266
1267     /**
1268      * this temporary array tracks the pointer within nestOffsets to the nest
1269      * block offset for intervals[i]'s container.
1270      */
1271     int[] startPt = new int[len];
1272
1273     startPt[root] = root;
1274
1275     int nextStart = counts[root];
1276     
1277     if (unnested > 0)
1278     {
1279       nestOffsets[root] = counts[unnested];
1280       nextStart += counts[unnested];
1281       startPt[unnested] = unnested;
1282     }
1283
1284     // Now get all the pointers right and set the nests[] pointer into intervals
1285     // correctly.
1286
1287     for (int i = 0; i < intervalCount; i++)
1288     {
1289       int ptOffset = startPt[myContainer[i]];
1290       int p = nestOffsets[ptOffset] + nestLengths[ptOffset]++;
1291       nests[p] = intervals[i];
1292       if (counts[i] > 0)
1293       {
1294         // this is a container
1295         startPt[i] = p;
1296         nestOffsets[p] = nextStart;
1297         nextStart += counts[i];
1298       }
1299     }
1300
1301     // System.out.println("intervals " + Arrays.toString(intervals));
1302     // System.out.println("nests " + Arrays.toString(nests));
1303     // System.out.println("conts " + Arrays.toString(myContainer));
1304     // System.out.println("offsets " + Arrays.toString(nestOffsets));
1305     // System.out.println("lengths " + Arrays.toString(nestLengths));
1306     // System.out.println(
1307     // "done " + nestLengths[root]
1308     // + (unnested > 0 ? " " + nestLengths[unnested] : ""));
1309   }
1310
1311   /**
1312    * Child-Parent relationships to match IntervalStoreJ. Perhaps a bit arcane?
1313    * Objective is to minimize the depth when we can.
1314    * 
1315    * @param childStart
1316    * @param childEnd
1317    * @param parentStart
1318    * @param parentEnd
1319    * @return
1320    */
1321   private static boolean isNested(int childStart, int childEnd,
1322           int parentStart, int parentEnd)
1323   {
1324     return (parentStart <= childStart && parentEnd > childEnd
1325             || parentStart < childStart && parentEnd == childEnd);
1326   }
1327
1328   /**
1329    * Just a couple of pointers to help speed findOverlaps along a bit.
1330    * 
1331    */
1332   private void updateMinMaxStart()
1333   {
1334     if (intervalCount > 0)
1335     {
1336       minStart = intervals[0].getBegin();
1337       maxStart = intervals[intervalCount - 1].getBegin();
1338     }
1339     else
1340     {
1341       minStart = Integer.MAX_VALUE;
1342       maxStart = Integer.MIN_VALUE;
1343     }
1344   }
1345
1346   @Override
1347   public String toString()
1348   {
1349     return prettyPrint();
1350   }
1351
1352   /**
1353    * Tests for instance equality without using {@code instanceof}
1354    * 
1355    * @param i1
1356    * @param i2
1357    * @return
1358    * @throws ClassCastException
1359    */
1360   protected boolean sameInterval(IntervalI i1, IntervalI i2)
1361   {
1362     return ((SequenceFeature) i1).equals((SequenceFeature) i2, false);
1363   }
1364
1365 }