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