JAL-3397 final update
[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.COMPARATOR_BIGENDIAN
228                     : IntervalI.COMPARATOR_LITTLEENDIAN);
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           // System.out.println("index = " + index + " for " + interval + "\n"
324           // + Arrays.toString(intervals) + "\n"
325           // + Arrays.toString(offsets));
326           if (!allowDuplicates && index >= 0)
327           {
328             return false;
329           }
330           if (index < 0)
331           {
332             index = -1 - index;
333           }
334           else
335           {
336             index++;
337           }
338         }
339
340       }
341       else
342       {
343         if (!allowDuplicates && findInterval(interval) >= 0)
344         {
345           return false;
346         }
347         isSorted = false;
348       }
349
350       if (index == intervalCount)
351       {
352         intervals[intervalCount++] = interval;
353         // System.out.println("added " + intervalCount + " " + interval);
354       }
355       else
356       {
357         int pt = capacity - ++added;
358         intervals[pt] = interval;
359         // System.out.println("stashed " + pt + " " + interval + " for "
360         // + index + " " + intervals[index]);
361         if (offsets == null)
362         {
363           offsets = new int[capacity];
364         }
365
366         offsets[pt] = offsets[index];
367
368         offsets[index] = pt;
369       }
370
371       minStart = Math.min(minStart, start);
372       maxStart = Math.max(maxStart, start);
373       return true;
374     }
375   }
376
377   /**
378    * Clean up the intervals array into a simple ordered array.
379    * 
380    * @param dest
381    * @return
382    */
383   private IntervalI[] finalizeAddition(IntervalI[] dest)
384   {
385     if (dest == null)
386     {
387       dest = intervals;
388     }
389     if (added == 0)
390     {
391       if (intervalCount > 0 && dest != intervals)
392       {
393         System.arraycopy(intervals, 0, dest, 0, intervalCount);
394       }
395       capacity = dest.length;
396       return dest;
397     }
398     // System.out.println("finalizing " + intervalCount + " " + added);
399
400     // array is [(intervalCount)...null...(added)]
401
402     int ntotal = intervalCount + added;
403     for (int ptShift = ntotal, pt = intervalCount; pt >= 0;)
404     {
405       int pt0 = pt;
406       while (--pt >= 0 && offsets[pt] == 0)
407       {
408         ;
409       }
410       if (pt < 0)
411       {
412         pt = 0;
413       }
414       int nOK = pt0 - pt;
415       // shift upper intervals right
416       ptShift -= nOK;
417       if (nOK > 0)
418       {
419         System.arraycopy(intervals, pt, dest, ptShift, nOK);
420       }
421       if (added == 0)
422       {
423         break;
424       }
425       for (int offset = offsets[pt]; offset > 0; offset = offsets[offset])
426       {
427         dest[--ptShift] = intervals[offset];
428         --added;
429       }
430     }
431     offsets = null;
432     intervalCount = ntotal;
433     capacity = dest.length;
434     // System.out.println(Arrays.toString(dest));
435     return dest;
436   }
437
438   /**
439    * A binary search for a duplicate.
440    * 
441    * @param interval
442    * @return
443    */
444   public int binaryIdentitySearch(IntervalI interval)
445   {
446     return binaryIdentitySearch(interval, null);
447   }
448
449   /**
450    * for remove() and contains()
451    * 
452    * @param list
453    * @param interval
454    * @param bsIgnore
455    *          for deleted
456    * @return index or, if not found, -1 - "would be here"
457    */
458   public int binaryIdentitySearch(IntervalI interval, BitSet bsIgnore)
459   {
460     int start = 0;
461     int r0 = interval.getBegin();
462     int r1 = interval.getEnd();
463     int end = intervalCount - 1;
464     if (end < 0 || r0 < minStart)
465     {
466       return -1;
467     }
468     if (r0 > maxStart)
469     {
470       return -1 - intervalCount;
471     }
472     while (start <= end)
473     {
474       int mid = (start + end) >>> 1;
475       IntervalI r = intervals[mid];
476       switch (compareRange(r, r0, r1))
477       {
478       case -1:
479         start = mid + 1;
480         continue;
481       case 1:
482         end = mid - 1;
483         continue;
484       case 0:
485         IntervalI iv = intervals[mid];
486         if ((bsIgnore == null || !bsIgnore.get(mid))
487                 && iv.equalsInterval(interval))
488         {
489           return mid;
490           // found one; just scan up and down now, first checking the range, but
491           // also checking other possible aspects of equivalence.
492         }
493
494         for (int i = mid; ++i <= end;)
495         {
496           if ((iv = intervals[i]).getBegin() != r0 || iv.getEnd() != r1)
497           {
498             break;
499           }
500           if ((bsIgnore == null || !bsIgnore.get(i))
501                   && iv.equalsInterval(interval))
502           {
503             return i;
504           }
505         }
506         for (int i = mid; --i >= start;)
507         {
508           if ((iv = intervals[i]).getBegin() != r0
509                   || (bigendian ? r1 < iv.getEnd() : iv.getEnd() < r1))
510           {
511             return -1 - ++i;
512           }
513           if ((bsIgnore == null || !bsIgnore.get(i))
514                   && iv.equalsInterval(interval))
515           {
516             return i;
517           }
518         }
519         return -1 - mid;
520       }
521     }
522     return -1 - start;
523   }
524
525   @Override
526   public boolean canCheckForDuplicates()
527   {
528     return true;
529   }
530
531   /**
532    * Reset all arrays.
533    * 
534    */
535   @Override
536   public void clear()
537   {
538     intervalCount = added = 0;
539     isSorted = true;
540     isTainted = true;
541     offsets = null;
542     intervals = new IntervalI[8];
543     nestStarts = nestCounts = null;
544     nests = null;
545     minStart = maxEnd = Integer.MAX_VALUE;
546     maxStart = Integer.MIN_VALUE;
547   }
548
549   /**
550    * Compare an interval t to a from/to range for insertion purposes
551    * 
552    * @param t
553    * @param from
554    * @param to
555    * @return 0 if same, 1 if start is after from, or start equals from and
556    *         [bigendian: end is before to | littleendian: end is after to], else
557    *         -1
558    */
559   private int compareRange(IntervalI t, long from, long to)
560   {
561     int order = Long.signum(t.getBegin() - from);
562     return (order == 0
563             ? Long.signum(bigendian ? to - t.getEnd() : t.getEnd() - to)
564             : order);
565   }
566
567   @Override
568   public boolean contains(Object entry)
569   {
570     if (entry == null || intervalCount == 0 && added == 0 && deleted == 0)
571     {
572       return false;
573     }
574     if (!isSorted || deleted > 0)
575     {
576       sort();
577     }
578     int n = findInterval((IntervalI) entry);
579     return (n >= 0);
580   }
581
582   /**
583    * Check to see if a given interval is within another.
584    * 
585    * Not implemented.
586    * 
587    * @param outer
588    * @param inner
589    * @return
590    */
591   public boolean containsInterval(IntervalI outer, IntervalI inner)
592   {
593     return false; // not applicable
594   }
595
596   /**
597    * Ensure that all addition, deletion, and sorting has been done, and that the
598    * nesting arrays have been created so that we are ready for findOverlaps().
599    * 
600    */
601
602   private void ensureFinalized()
603   {
604     if (isTainted)
605     {
606       if (!isSorted || added > 0 || deleted > 0)
607       {
608         sort();
609       }
610       if (intervalCount > 0)
611       {
612         createArrays();
613       }
614       isTainted = false;
615     }
616   }
617
618   /**
619    * Find all overlaps within the given range, inclusively.
620    * 
621    * @return a list sorted in ascending order of start position
622    * 
623    */
624   @Override
625   public List<T> findOverlaps(long from, long to)
626   {
627     return findOverlaps(from, to, null);
628   }
629
630   /**
631    * Find all overlaps within the given range, inclusively.
632    * 
633    * @return a list sorted in the order provided by the features list comparator
634    * 
635    */
636
637   @SuppressWarnings("unchecked")
638   @Override
639   public List<T> findOverlaps(long from, long to, List<T> result)
640   {
641     if (result == null)
642     {
643       result = new ArrayList<>();
644     }
645     switch (intervalCount + added)
646     {
647     case 0:
648       return result;
649     case 1:
650       IntervalI sf = intervals[0];
651       if (sf.getBegin() <= to && sf.getEnd() >= from)
652       {
653         result.add((T) sf);
654       }
655       return result;
656     }
657
658     ensureFinalized();
659
660     if (from > maxEnd || to < minStart)
661     {
662       return result;
663     }
664     int root = 0;
665     if (createUnnested)
666     {
667       if (nestCounts[0] > 0)
668       {
669         searchNonnested(nestCounts[0], nests, from, to,
670                 (List<IntervalI>) result);
671       }
672       root = 1;
673     }
674     if (nestCounts[root] > 0)
675     {
676       search(nests, from, to, root, result);
677     }
678     return result;
679   }
680
681   /**
682    * A simpler search, since we know we don't have any subintervals. Not
683    * necessary, actually.
684    * 
685    * @param nestStarts
686    * @param nestCounts
687    * @param nests
688    * @param from
689    * @param to
690    * @param result
691    */
692   private static void searchNonnested(int n,
693           IntervalI[] nests, long from, long to, List<IntervalI> result)
694   {
695     int end = 2 + n - 1;
696     for (int pt = binarySearchFirstEndNotBefore(nests, from, 2,
697             end); pt <= end; pt++)
698     {
699       IntervalI ival = nests[pt];
700       if (ival.getBegin() > to)
701       {
702         break;
703       }
704       result.add(ival);
705     }
706   }
707
708   /**
709    * The main search of the nests[] array's subarrays
710    * 
711    * @param nests
712    * @param from
713    * @param to
714    * @param nest
715    * @param result
716    */
717   @SuppressWarnings("unchecked")
718   private void search(IntervalI[] nests, long from, long to, int nest,
719           List<T> result)
720   {
721     int start = nestStarts[nest];
722     int n = nestCounts[nest];
723     int end = start + n - 1;
724     IntervalI first = nests[start];
725     IntervalI last = nests[end];
726
727     // quick tests for common cases:
728     // out of range
729     if (last.getEnd() < from || first.getBegin() > to)
730     {
731       return;
732     }
733     int pt;
734     switch (n)
735     {
736     case 1:
737       // just one interval and hasn't failed begin/end test
738       pt = start;
739       break;
740     case 2:
741       // just two and didn't fail begin/end test
742       // so there is only one option: either the first or the second is our
743       // winner
744       pt = (first.getEnd() >= from ? start : end);
745       break;
746     default:
747       // do the binary search
748       pt = binarySearchFirstEndNotBefore(nests, from, start, end);
749       break;
750     }
751     for (; pt <= end; pt++)
752     {
753       IntervalI ival = nests[pt];
754       // check for out of range
755       if (ival.getBegin() > to)
756       {
757         break;
758       }
759       result.add((T) ival);
760       if (nestCounts[pt] > 0)
761       {
762         // check subintervals in this nest
763         search(nests, from, to, pt, result);
764       }
765     }
766   }
767
768   /**
769    * return the i-th interval in the designated order (bigendian or
770    * littleendian)
771    */
772   @Override
773   public IntervalI get(int i)
774   {
775     if (i < 0 || i >= intervalCount + added)
776     {
777       return null;
778     }
779     ensureFinalized();
780     return intervals[i];
781   }
782
783   /**
784    * Return the deepest level of nesting.
785    * 
786    */
787   @Override
788   public int getDepth()
789   {
790     ensureFinalized();
791     BitSet bsTested = new BitSet();
792     return Math.max((createUnnested ? getDepth(1, bsTested) : 0),
793             getDepth(0, bsTested));
794   }
795
796   /**
797    * Iteratively dive deeply.
798    * 
799    * @param pt
800    * @param bsTested
801    * @return
802    */
803   private int getDepth(int pt, BitSet bsTested)
804   {
805     int maxDepth = 0;
806     int depth;
807     int n = nestCounts[pt];
808     if (n == 0 || bsTested.get(pt))
809     {
810       return 1;
811     }
812     bsTested.set(pt);
813     for (int st = nestStarts[pt], i = st + n; --i >= st;)
814     {
815       if ((depth = getDepth(i, bsTested)) > maxDepth)
816       {
817         maxDepth = depth;
818       }
819     }
820     return maxDepth + 1;
821   }
822
823   /**
824    * Get the number of root-level nests.
825    * 
826    */
827   @Override
828   public int getWidth()
829   {
830     ensureFinalized();
831     // System.out.println(
832     // "ISList w[0]=" + nestCounts[0] + " w[1]=" + nestCounts[1]);
833     return nestCounts[0] + (createUnnested ? nestCounts[1] : 0);
834   }
835
836   @Override
837   public boolean isValid()
838   {
839     ensureFinalized();
840     return true;
841   }
842
843   /**
844    * Answers an iterator over the intervals in the store, with no particular
845    * ordering guaranteed. The iterator does not support the optional
846    * <code>remove</code> operation (throws
847    * <code>UnsupportedOperationException</code> if attempted).
848    */
849   @Override
850   public Iterator<T> iterator()
851   {
852     ensureFinalized();
853     return new Iterator<T>()
854     {
855
856       private int next;
857
858       @Override
859       public boolean hasNext()
860       {
861         return next < intervalCount;
862       }
863
864       @SuppressWarnings("unchecked")
865       @Override
866       public T next()
867       {
868         if (next >= intervalCount)
869         {
870           throw new NoSuchElementException();
871         }
872         return (T) intervals[next++];
873       }
874
875     };
876   }
877
878   /**
879    * Indented printing of the intervals.
880    * 
881    */
882   @Override
883   public String prettyPrint()
884   {
885     ensureFinalized();
886     StringBuffer sb = new StringBuffer();
887     if (createUnnested)
888     {
889       sb.append("unnested:");
890       dump(0, sb, "\n");
891       sb.append("\nnested:");
892       dump(1, sb, "\n");
893     }
894     else
895     {
896       dump(0, sb, "\n");
897     }
898     return sb.toString();
899   }
900
901   /**
902    * Iterative nest dump.
903    * 
904    * @param nest
905    * @param sb
906    * @param sep
907    */
908   private void dump(int nest, StringBuffer sb, String sep)
909   {
910     int pt = nestStarts[nest];
911     int n = nestCounts[nest];
912     sep += "  ";
913
914     for (int i = 0; i < n; i++)
915     {
916       sb.append(sep).append(nests[pt + i].toString());
917       if (nestCounts[pt + i] > 0)
918         dump(pt + i, sb, sep + "  ");
919     }
920   }
921
922   @Override
923   public synchronized boolean remove(Object o)
924   {
925     // if (o == null)
926     // {
927     // throw new NullPointerException();
928     // }
929     return (o != null && intervalCount > 0
930             && removeInterval((IntervalI) o));
931   }
932
933   /**
934    * Find the interval or return where it should go, possibly into the add
935    * buffer
936    * 
937    * @param interval
938    * @return index (nonnegative) or index where it would go (negative)
939    */
940
941   private int findInterval(IntervalI interval)
942   {
943
944     if (isSorted)
945     {
946       int pt = binaryIdentitySearch(interval, null);
947       // if (addPt == intervalCount || offsets[pt] == 0)
948       // return pt;
949       if (pt >= 0 || added == 0 || pt == -1 - intervalCount)
950       {
951         return pt;
952       }
953       pt = -1 - pt;
954       int start = interval.getBegin();
955       int end = interval.getEnd();
956
957       int match = pt;
958
959       while ((pt = offsets[pt]) != 0)
960       {
961         IntervalI iv = intervals[pt];
962         switch (compareRange(iv, start, end))
963         {
964         case -1:
965           break;
966         case 0:
967           if (iv.equalsInterval(interval))
968           {
969             return pt;
970           }
971           // fall through
972         case 1:
973           match = pt;
974           continue;
975         }
976       }
977       return -1 - match;
978     }
979     else
980     {
981       int i = intervalCount;
982       while (--i >= 0 && !intervals[i].equalsInterval(interval))
983       {
984         ;
985       }
986       return i;
987     }
988   }
989
990   /**
991    * Uses a binary search to find the entry and removes it if found.
992    * 
993    * @param interval
994    * @return
995    */
996   protected boolean removeInterval(IntervalI interval)
997   {
998
999     if (!isSorted || added > 0)
1000     {
1001       sort();
1002     }
1003     int i = binaryIdentitySearch(interval, bsDeleted);
1004     if (i < 0)
1005     {
1006       return false;
1007     }
1008     if (deleted == 0)
1009     {
1010       if (bsDeleted == null)
1011       {
1012         bsDeleted = new BitSet(intervalCount);
1013       }
1014       else
1015       {
1016         bsDeleted.clear();
1017       }
1018     }
1019     bsDeleted.set(i);
1020     deleted++;
1021     return (isTainted = true);
1022   }
1023
1024   /**
1025    * Fill in the gaps of the intervals array after one or more deletions.
1026    * 
1027    */
1028   private void finalizeDeletion()
1029   {
1030     if (deleted == 0)
1031     {
1032       return;
1033     }
1034
1035     // ......xxx.....xxxx.....xxxxx....
1036     // ......^i,pt
1037     // ...... .......
1038     // ............
1039     for (int i = bsDeleted.nextSetBit(0), pt = i; i >= 0;)
1040     {
1041       i = bsDeleted.nextClearBit(i + 1);
1042       int pt1 = bsDeleted.nextSetBit(i + 1);
1043       if (pt1 < 0)
1044       {
1045         pt1 = intervalCount;
1046       }
1047       int n = pt1 - i;
1048       System.arraycopy(intervals, i, intervals, pt, n);
1049       pt += n;
1050       if (pt1 == intervalCount)
1051       {
1052         for (i = pt1; --i >= pt;)
1053         {
1054           intervals[i] = null;
1055         }
1056         intervalCount -= deleted;
1057         deleted = 0;
1058         bsDeleted.clear();
1059         break;
1060       }
1061       i = pt1;
1062     }
1063
1064   }
1065
1066   /**
1067    * Recreate the key nest arrays.
1068    * 
1069    */
1070   @Override
1071   public boolean revalidate()
1072   {
1073     isTainted = true;
1074     isSorted = false;
1075     ensureFinalized();
1076     return true;
1077   }
1078
1079   /**
1080    * Return the total number of intervals in the store.
1081    * 
1082    */
1083   @Override
1084   public int size()
1085   {
1086     return intervalCount + added - deleted;
1087   }
1088
1089   /**
1090    * AbstractCollection override to ensure that we have finalized the store.
1091    */
1092   @Override
1093   public Object[] toArray()
1094   {
1095     ensureFinalized();
1096     return super.toArray();
1097   }
1098
1099   /**
1100    * Sort intervals by start.
1101    */
1102   private void sort()
1103   {
1104     if (added > 0)
1105     {
1106       intervals = finalizeAddition(new IntervalI[intervalCount + added]);
1107     }
1108     else if (deleted > 0)
1109     {
1110       finalizeDeletion();
1111     }
1112     else
1113     {
1114       // SOMETHING HAPPENS WHEN Arrays.sort is run that
1115       // adds 100 ms to a 150 ms run time.
1116       // I don't know why.
1117       Arrays.sort(intervals, 0, intervalCount, icompare);
1118     }
1119     updateMinMaxStart();
1120     isSorted = true;
1121   }
1122
1123   // 0 5-5
1124   // 1 6-8
1125   // 2 10-80
1126   // 3 10-100
1127   // 4 10-100
1128   // 5 20-30
1129   // 6 35-40
1130   // 7 50-80
1131   // 8 51-51
1132   // 9 52-52
1133   // 10 55-60
1134   // 11 56-56
1135   // 12 70-120
1136   // 13 78-78
1137   //
1138   // cont [-1, -1, -1, -1, 3, 4, 4, 4, 7, 7, 7, 10, -1, 12]
1139   // nests [0, 0, 1, 2, 3, 12, 4, 5, 6, 7, 8, 9, 10, 11, 13]
1140   // starts [1, 0, 0, 0, 6, 14, 7, 0, 0, 10, 0, 0, 13, 0, 0]
1141   // counts [5, 0, 0, 0, 1, 1, 3, 0, 0, 3, 0, 0, 1, 0, 0]
1142
1143   /**
1144    * Create the key arrays: nests, nestStarts, and nestCounts. The starting
1145    * point is getting the container array, which may hold -1 (top level nesting)
1146    * and -2 (unnested set, if doing that).
1147    * 
1148    * This is a pretty complicated method; it was way simpler before I decided to
1149    * support nesting as an option.
1150    *
1151    */
1152   private void createArrays()
1153   {
1154
1155     /**
1156      * When unnesting, we need a second top-level listing.
1157      * 
1158      */
1159     int incr = (createUnnested ? 2 : 1);
1160
1161     /**
1162      * The three key arrays produced by this method:
1163      */
1164
1165     nests = new IntervalI[intervalCount + incr];
1166     nestStarts = new int[intervalCount + incr];
1167     nestCounts = new int[intervalCount + incr];
1168
1169     /**
1170      * a temporary array used in Phase Two.
1171      */
1172
1173     int[] counts = new int[intervalCount + incr];
1174
1175     /**
1176      * the objective of Phase One
1177      */
1178     int[] myContainer = new int[intervalCount];
1179
1180     myContainer[0] = -incr;
1181     counts[0] = 1;
1182     int beginLast = intervals[0].getBegin();
1183     int endLast = intervals[0].getEnd();
1184     int ptLastNot2 = -1;
1185     int endLast2 = endLast;
1186     int beginLast2 = beginLast;
1187
1188     // Phase One: Get the temporary container array myContainer.
1189
1190     for (int i = 1; i < intervalCount; i++)
1191     {
1192       int pt = i - 1;
1193       int end = intervals[i].getEnd();
1194       int begin = intervals[i].getBegin();
1195
1196       // set the pointer to the element that is containing
1197       // this interval, or -2 (unnested) or -1 (root-level nest)
1198
1199       myContainer[i] = -incr;
1200
1201       // OK, now figure it all out...
1202
1203       boolean isNested;
1204       if (createUnnested)
1205       {
1206         // Using a method isNested(...) here, because there are different
1207         // ways of defining "nested" when start or end are the
1208         // same. The definition used here would not be my first choice,
1209         // but it matches results for IntervalStoreJ
1210         // perfectly, down to the exact number of times that the
1211         // binary search runs through its start/mid/end loops in findOverlap.
1212
1213         // beginLast2 and endLast2 refer to the root-level or unnested level
1214
1215         if (!isNested(begin, end, beginLast2, endLast2))
1216         {
1217           isNested = false;
1218         }
1219         else
1220         {
1221           // this is tricky; making sure we properly get the
1222           // nests that are to be removed from the top-level
1223           // unnested list assigned a container -1, while all
1224           // top-level nests get -2.
1225
1226           pt = ptLastNot2;
1227           isNested = (pt < 0 || isNested(begin, end,
1228                   intervals[pt].getBegin(), intervals[pt].getEnd()));
1229           if (!isNested)
1230           {
1231             myContainer[i] = -1;
1232           }
1233         }
1234       }
1235       else
1236       {
1237         isNested = isNested(begin, end, beginLast, endLast);
1238       }
1239
1240       // ...almost done...
1241
1242       if (isNested)
1243       {
1244         myContainer[i] = pt;
1245       }
1246       else
1247       {
1248
1249         // monotonic -- find the parent that is doing the nesting
1250
1251         while ((pt = myContainer[pt]) >= 0)
1252         {
1253           if (isNested(begin, end, intervals[pt].getBegin(),
1254                   intervals[pt].getEnd()))
1255           {
1256             myContainer[i] = pt;
1257             // fully contained by a previous interval
1258             // System.out.println("mycontainer " + i + " = " + pt);
1259             break;
1260           }
1261         }
1262       }
1263
1264       // update the counts and pointers
1265
1266       counts[myContainer[i] + incr]++;
1267       if (myContainer[i] == -2)
1268       {
1269         endLast2 = end;
1270         beginLast2 = begin;
1271       }
1272       else
1273       {
1274         ptLastNot2 = i;
1275         endLast = end;
1276         beginLast = begin;
1277       }
1278     }
1279
1280     // Phase Two: construct the nests[] array and its associated
1281     // starting pointer array and nest element counts. These counts
1282     // are actually produced above, but we reconstruct it as a set
1283     // of dynamic pointers during construction.
1284     
1285     // incr is either 1 (no separate unnested set) or 2 (include unnested)
1286
1287     int nextStart = counts[0] + incr;
1288     /**
1289      * this array tracks the pointer within nestStarts to the nest block start
1290      * in nests[].
1291      */
1292     int[] startPt = new int[intervalCount + incr];
1293     nestStarts[0] = incr;
1294     
1295     // When not unnesting, nestStarts[0] = 1, and the length
1296     // will start out here as 0 but increment as we go.
1297     // We do this even though we know its size already, because that
1298     // value serves as a dynamic pointer as well.
1299
1300     if (createUnnested)
1301     {
1302
1303       // Unnesting requires two separate lists with proper pointers and counts.
1304       // The first, nestStarts[0] = 0, is for the unnested set (container -2);
1305       // the second (container -1, nestStarts[1]) is for the nest root.
1306
1307       startPt[1] = 1;
1308       nestStarts[1] = nextStart;
1309       nextStart += counts[1];
1310     }
1311
1312     // Now get all the pointers right and set the nests[] pointer into intervals
1313     // correctly.
1314
1315     for (int i = 0; i < intervalCount; i++)
1316     {
1317       int n = counts[i + incr];
1318       int ptNest = startPt[myContainer[i] + incr];
1319       int p = nestStarts[ptNest] + nestCounts[ptNest]++;
1320       nests[p] = intervals[i];
1321       if (n > 0)
1322       {
1323         startPt[i + incr] = p;
1324         nestStarts[p] = nextStart;
1325         nextStart += n;
1326       }
1327     }
1328
1329     // System.out.println("intervals " + Arrays.toString(intervals));
1330     // System.out.println("nests " + Arrays.toString(nests));
1331     // System.out.println("conts " + Arrays.toString(myContainer));
1332     // System.out.println("starts " + Arrays.toString(nestStarts));
1333     // System.out.println("counts " + Arrays.toString(nestCounts));
1334     // System.out.println("done " + nestCounts[0]);
1335   }
1336
1337   /**
1338    * Child-Parent relationships to match IntervalStoreJ. Perhaps a bit arcane?
1339    * Objective is to minimize the depth when we can.
1340    * 
1341    * @param childStart
1342    * @param childEnd
1343    * @param parentStart
1344    * @param parentEnd
1345    * @return
1346    */
1347   private static boolean isNested(int childStart, int childEnd,
1348           int parentStart, int parentEnd)
1349   {
1350     return (parentStart <= childStart && parentEnd > childEnd
1351             || parentStart < childStart && parentEnd == childEnd);
1352   }
1353
1354   /**
1355    * Just a couple of pointers to help speed findOverlaps along a bit.
1356    * 
1357    */
1358   private void updateMinMaxStart()
1359   {
1360     if (intervalCount > 0)
1361     {
1362       minStart = intervals[0].getBegin();
1363       maxStart = intervals[intervalCount - 1].getBegin();
1364     }
1365     else
1366     {
1367       minStart = Integer.MAX_VALUE;
1368       maxStart = Integer.MIN_VALUE;
1369     }
1370   }
1371
1372   @Override
1373   public String toString()
1374   {
1375     return prettyPrint();
1376   }
1377
1378
1379 }