JAL-3210 Improvements to eclipse detection. New src tree and SwingJS updated from...
[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 second idea, doing a double binary sort for the full interval. Seemed like
50  * a good idea, but is 50% slower.
51  * 
52  * A Collection class to store interval-associated data, with options for "lazy"
53  * sorting so as to speed incremental construction of the data prior to issuing
54  * a findOverlap call.
55  * 
56  * 
57  * Accepts duplicate entries but not null values.
58  * 
59  * 
60  * 
61  * @author Bob Hanson 2019.08.06
62  *
63  * @param <T>
64  *          any type providing <code>getBegin()</code>, <code>getEnd()</code>
65  *          <code>getContainedBy()</code>, and <code>setContainedBy()</code>
66  */
67 public class IntervalStore<T extends IntervalI>
68         extends AbstractCollection<T> implements IntervalStoreI<T>
69 {
70
71   /**
72    * Search for the last interval that starts before or at the specified from/to
73    * range and the first interval that starts after it. In the situation that
74    * there are multiple intervals starting at from, this method returns the
75    * first of those.
76    * 
77    * @param a
78    * @param from
79    * @param to
80    * @param ret
81    * @return
82    */
83   public int binaryLastIntervalSearch(long from,
84           long to, int[] ret)
85   {
86     int start = 0, start2 = 0;
87     int matched = 0;
88     int end = intervalCount - 1, end2 = intervalCount;
89     int mid, begin;
90     IntervalI e;
91     while (start <= end)
92     {
93       mid = (start + end) >>> 1;
94       e = intervals[mid];
95       begin = e.getBegin();
96       switch (Long.signum(begin - from))
97       {
98       case -1:
99         matched = mid;
100         start = mid + 1;
101         break;
102       case 0:
103       case 1:
104         end = mid - 1;
105         if (begin > to)
106         {
107           end2 = mid;
108         }
109         else
110         {
111           start2 = mid;
112         }
113         break;
114       }
115     }
116     ret[0] = end2;
117     start = Math.max(start2, end);
118     end = end2 - 1;
119
120     while (start <= end)
121     {
122       mid = (start + end) >>> 1;
123       e = intervals[mid];
124       begin = e.getBegin();
125       if (begin > to)
126       {
127         ret[0] = mid;
128         end = mid - 1;
129       }
130       else
131       {
132         start = mid + 1;
133       }
134
135     }
136     return matched;
137   }
138
139   /**
140    * My preference is for a bigendian comparison, but you may differ.
141    */
142   private Comparator<? super IntervalI> icompare;
143
144   /**
145    * bigendian is what NCList does; change icompare to switch to that
146    */
147   private boolean bigendian;
148
149   private final boolean DO_PRESORT;
150
151   private boolean isSorted;
152
153   private int minStart = Integer.MAX_VALUE, maxStart = Integer.MIN_VALUE,
154           maxEnd = Integer.MAX_VALUE;
155
156   // private Comparator<IntervalI> icompare = new IntervalComparator();
157
158   private boolean isTainted;
159
160   private int capacity = 8;
161
162   private IntervalI[] intervals = new IntervalI[capacity];
163
164   private int[] offsets;
165
166   private int[] ret = new int[1];
167
168   private int intervalCount;
169
170   private int added;
171
172   private int deleted;
173
174   private BitSet bsDeleted;
175
176   /**
177    * Constructor
178    */
179   public IntervalStore()
180   {
181     this(true);
182   }
183
184   public IntervalStore(boolean presort)
185   {
186     this(null, presort);
187   }
188
189   /**
190    * Constructor given a list of intervals. Note that the list may get sorted as
191    * a side-effect of calling this constructor.
192    */
193   public IntervalStore(List<T> intervals)
194   {
195     this(intervals, true);
196   }
197
198   /**
199    * Allows a presort option, which can speed up initial loading of individual
200    * features but will delay the first findOverlap if set to true.
201    * 
202    * @param intervals
203    * @param presort
204    */
205   public IntervalStore(List<T> intervals, boolean presort)
206   {
207     this(intervals, presort, null, false);
208   }
209
210   /**
211    * 
212    * @param intervals
213    *          intervals to initialize with (others may still be added)
214    * @param presort
215    *          whether or not to presort the list as additions are made
216    * @param comparator
217    *          IntervalI.COMPARATOR_LITTLEENDIAN or
218    *          IntervalI.COMPARATOR_BIGENDIAN, but this could also be one that
219    *          sorts by description as well, for example.
220    * @param bigendian
221    *          true if the comparator sorts [10-30] before [10-20]
222    */
223   public IntervalStore(List<T> intervals, boolean presort,
224           Comparator<? super IntervalI> comparator, boolean bigendian)
225   {
226     if (intervals != null)
227     {
228       intervals.toArray(
229               this.intervals = new IntervalI[capacity = intervalCount = intervals
230                       .size()]);
231     }
232     DO_PRESORT = presort;
233     icompare = (comparator != null ? comparator
234             : bigendian ? IntervalI.COMPARATOR_BIGENDIAN
235                     : IntervalI.COMPARATOR_LITTLEENDIAN);
236     this.bigendian = bigendian;
237
238     if (DO_PRESORT && intervalCount > 1)
239     {
240       sort();
241     }
242     isSorted = DO_PRESORT;
243     isTainted = true;
244   }
245
246   /**
247    * Adds one interval to the store, allowing duplicates.
248    * 
249    * @param interval
250    */
251   @Override
252   public boolean add(T interval)
253   {
254     return add(interval, true);
255   }
256
257   /**
258    * Adds one interval to the store, optionally checking for duplicates.
259    * 
260    * @param interval
261    * @param allowDuplicates
262    */
263   public boolean add(T interval, boolean allowDuplicates)
264   {
265     if (interval == null)
266     {
267       return false;
268     }
269
270     if (deleted > 0)
271     {
272       finalizeDeletion();
273     }
274     if (!isTainted)
275     {
276       offsets = null;
277       isTainted = true;
278     }
279
280     synchronized (intervals)
281     {
282       int index = intervalCount;
283       int start = interval.getBegin();
284
285       if (intervalCount + added + 1 >= capacity)
286       {
287         intervals = finalizeAddition(
288                 new IntervalI[capacity = capacity << 1]);
289
290       }
291
292       if (DO_PRESORT && isSorted)
293       {
294         if (intervalCount == 0)
295         {
296           // ignore
297         }
298         else
299         {
300           index = findInterval(interval);
301           if (!allowDuplicates && index >= 0)
302           {
303             return false;
304           }
305           if (index < 0)
306           {
307             index = -1 - index;
308           }
309           else
310           {
311             index++;
312           }
313         }
314
315       }
316       else
317       {
318         if (!allowDuplicates && findInterval(interval) >= 0)
319         {
320           return false;
321         }
322         isSorted = false;
323       }
324
325       if (index == intervalCount)
326       {
327         intervals[intervalCount++] = interval;
328         // System.out.println("added " + intervalCount + " " + interval);
329       }
330       else
331       {
332         int pt = capacity - ++added;
333         intervals[pt] = interval;
334         // System.out.println("stashed " + pt + " " + interval + " for "
335         // + index + " " + intervals[index]);
336         if (offsets == null)
337         {
338           offsets = new int[capacity];
339         }
340
341         offsets[pt] = offsets[index];
342
343         offsets[index] = pt;
344       }
345
346       minStart = Math.min(minStart, start);
347       maxStart = Math.max(maxStart, start);
348       return true;
349     }
350   }
351
352   private IntervalI[] finalizeAddition(IntervalI[] dest)
353   {
354     if (dest == null)
355     {
356       dest = intervals;
357     }
358     if (added == 0)
359     {
360       if (intervalCount > 0 && dest != intervals)
361       {
362         System.arraycopy(intervals, 0, dest, 0, intervalCount);
363       }
364       capacity = dest.length;
365       return dest;
366     }
367
368     // array is [(intervalCount)...null...(added)]
369
370     int ntotal = intervalCount + added;
371     for (int ptShift = intervalCount + added, pt = intervalCount; pt >= 0;)
372     {
373       int pt0 = pt;
374       while (--pt >= 0 && offsets[pt] == 0)
375       {
376         ;
377       }
378       if (pt < 0)
379       {
380         pt = 0;
381       }
382       int nOK = pt0 - pt;
383       // shift upper intervals right
384       ptShift -= nOK;
385       if (nOK > 0)
386       {
387         System.arraycopy(intervals, pt, dest, ptShift, nOK);
388       }
389       if (added == 0)
390       {
391         break;
392       }
393         for (int offset = offsets[pt]; offset > 0; offset = offsets[offset])
394         {
395           dest[--ptShift] = intervals[offset];
396           --added;
397         }
398     }
399     offsets = null;
400     intervalCount = ntotal;
401     capacity = dest.length;
402     return dest;
403   }
404
405   public int binaryIdentitySearch(IntervalI interval)
406   {
407     return binaryIdentitySearch(interval, null);
408   }
409   /**
410    * for remove() and contains()
411    * 
412    * @param list
413    * @param interval
414    * @param bsIgnore
415    *          for deleted
416    * @return index or, if not found, -1 - "would be here"
417    */
418   public int binaryIdentitySearch(IntervalI interval, BitSet bsIgnore)
419   {
420     int start = 0;
421     int r0 = interval.getBegin();
422     int r1 = interval.getEnd();
423     int end = intervalCount - 1;
424     if (end < 0 || r0 < minStart)
425     {
426       return -1;
427     }
428     if (r0 > maxStart)
429     {
430       return -1 - intervalCount;
431     }
432     while (start <= end)
433     {
434       int mid = (start + end) >>> 1;
435       IntervalI r = intervals[mid];
436       switch (compareRange(r, r0, r1))
437       {
438       case -1:
439         start = mid + 1;
440         continue;
441       case 1:
442         end = mid - 1;
443         continue;
444       case 0:
445         IntervalI iv = intervals[mid];
446         if ((bsIgnore == null || !bsIgnore.get(mid))
447                 && iv.equalsInterval(interval))
448          {
449           return mid;
450         // found one; just scan up and down now, first checking the range, but
451         // also checking other possible aspects of equivalence.
452         }
453
454         for (int i = mid; ++i <= end;)
455         {
456           if ((iv = intervals[i]).getBegin() != r0 || iv.getEnd() != r1)
457           {
458             break;
459           }
460           if ((bsIgnore == null || !bsIgnore.get(i))
461                   && iv.equalsInterval(interval))
462           {
463             return i;
464           }
465         }
466         for (int i = mid; --i >= start;)
467         {
468           if ((iv = intervals[i]).getBegin() != r0 || iv.getEnd() < r1)
469           {
470             return -1 - ++i;
471           }
472           if ((bsIgnore == null || !bsIgnore.get(i))
473                   && iv.equalsInterval(interval))
474           {
475             return i;
476           }
477         }
478         return -1 - start;
479       }
480     }
481     return -1 - start;
482   }
483
484   // private int binaryInsertionSearch(long from, long to)
485   // {
486   // int matched = intervalCount;
487   // int end = matched - 1;
488   // int start = matched;
489   // if (end < 0 || from > intervals[end].getEnd()
490   // || from < intervals[start = 0].getBegin())
491   // return start;
492   // while (start <= end)
493   // {
494   // int mid = (start + end) >>> 1;
495   // switch (compareRange(intervals[mid], from, to))
496   // {
497   // case 0:
498   // return mid;
499   // case 1:
500   // matched = mid;
501   // end = mid - 1;
502   // continue;
503   // case -1:
504   // start = mid + 1;
505   // continue;
506   // }
507   //
508   // }
509   // return matched;
510   // }
511
512
513   @Override
514   public void clear()
515   {
516     intervalCount = added = 0;
517     isSorted = true;
518     isTainted = true;
519     offsets = null;
520     minStart = maxEnd = Integer.MAX_VALUE;
521     maxStart = Integer.MIN_VALUE;
522   }
523   /**
524    * Compare an interval t to a from/to range for insertion purposes
525    * 
526    * @param t
527    * @param from
528    * @param to
529    * @return 0 if same, 1 if start is after from, or start equals from and
530    *         [bigendian: end is before to | littleendian: end is after to], else
531    *         -1
532    */
533   private int compareRange(IntervalI t, long from, long to)
534   {
535     if (t == null)
536     {
537       System.out.println("???");
538     }
539     int order = Long.signum(t.getBegin() - from);
540     return (order == 0
541             ? Long.signum(bigendian ? to - t.getEnd() : t.getEnd() - to)
542             : order);
543   }
544
545   @Override
546   public boolean contains(Object entry)
547   {
548     if (entry == null || intervalCount == 0)
549     {
550       return false;
551     }
552     if (!isSorted || deleted > 0)
553     {
554       sort();
555     }
556     return (findInterval((IntervalI) entry) >= 0);
557   }
558
559   public boolean containsInterval(IntervalI outer, IntervalI inner)
560   {
561     ensureFinalized();
562     int index = binaryIdentitySearch(inner, null);
563     if (index >= 0)
564     {
565       while ((index = index - Math.abs(offsets[index])) >= 0)
566       {
567         if (intervals[index] == outer)
568         {
569           return true;
570         }
571       }
572     }
573     return false;
574   }
575
576   private void ensureFinalized()
577   {
578     if (isTainted)
579     {
580       if (!isSorted || added > 0 || deleted > 0)
581       {
582         sort();
583       }
584       if (offsets == null || offsets.length < intervalCount)
585       {
586         offsets = new int[intervalCount];
587       }
588       linkFeatures();
589       isTainted = false;
590     }
591   }
592
593   /**
594    * Find all overlaps within the given range, inclusively.
595    * 
596    * @return a list sorted in ascending order of start position
597    * 
598    */
599   @Override
600   public List<T> findOverlaps(long from, long to)
601   {
602     List<T> list = findOverlaps(from, to, null);
603     Collections.reverse(list);
604     return list;
605   }
606
607   /**
608    * Find all overlaps within the given range, inclusively.
609    * 
610    * @return a list sorted in descending order of start position
611    * 
612    */
613   
614   @SuppressWarnings("unchecked")
615   @Override
616   public List<T> findOverlaps(long from, long to, List<T> result)
617   {
618     if (result == null)
619     {
620       result = new ArrayList<>();
621     }
622     switch (intervalCount + added)
623     {
624     case 0:
625       return result;
626     case 1:
627       IntervalI sf = intervals[0];
628       if (sf.getBegin() <= to && sf.getEnd() >= from)
629       {
630         result.add((T) sf);
631       }
632       return result;
633     }
634
635     ensureFinalized();
636
637     if (from > maxEnd || to < minStart)
638     {
639       return result;
640     }
641     int index = binaryLastIntervalSearch(from, to, ret);
642     int index1 = ret[0];
643     if (index1 < 0)
644     {
645       return result;
646     }
647
648     if (index1 > index + 1)
649     {
650       while (--index1 > index)
651       {
652         result.add((T) intervals[index1]);
653       }
654     }
655     boolean isMonotonic = false;
656     while (index >= 0)
657     {
658       IntervalI sf = intervals[index];
659       if (sf.getEnd() >= from)
660       {
661         result.add((T) sf);
662       }
663       else if (isMonotonic)
664       {
665         break;
666       }
667       int offset = offsets[index];
668       isMonotonic = (offset < 0);
669       index -= (isMonotonic ? -offset : offset);
670     }
671     return result;
672   }
673
674   @Override
675   public IntervalI get(int i)
676   {
677     if (i < 0 || i >= intervalCount + added)
678     {
679       return null;
680     }
681     ensureFinalized();
682     return intervals[i];
683   }
684
685   private int getContainedBy(int index, int begin)
686   {
687     while (index >= 0)
688     {
689       IntervalI sf = intervals[index];
690       if (sf.getEnd() >= begin)
691       {
692         // System.out.println("\nIS found " + sf0.getIndex1() + ":" + sf0
693         // + "\nFS in " + sf.getIndex1() + ":" + sf);
694         return index;
695       }
696       index -= Math.abs(offsets[index]);
697     }
698     return IntervalI.NOT_CONTAINED;
699   }
700
701   @Override
702   public int getDepth()
703   {
704     ensureFinalized();
705     if (intervalCount < 2)
706     {
707       return intervalCount;
708     }
709     int maxDepth = 1;
710     IntervalI root = null;
711     for (int i = 0; i < intervalCount; i++)
712     {
713       IntervalI element = intervals[i];
714       if (offsets[i] == IntervalI.NOT_CONTAINED)
715       {
716         root = element;
717       }
718       int depth = 1;
719       int index = i;
720       int offset;
721       while ((index = index - Math.abs(offset = offsets[index])) >= 0)
722       {
723         element = intervals[index];
724         if (++depth > maxDepth && (element == root || offset < 0))
725         {
726           maxDepth = depth;
727           break;
728         }
729       }
730     }
731     return maxDepth;
732   }
733
734   @Override
735   public int getWidth()
736   {
737     ensureFinalized();
738     int w = 0;
739     for (int i = offsets.length; --i >= 0;)
740     {
741       if (offsets[i] > 0)
742       {
743         w++;
744       }
745     }
746     return w;
747   }
748
749   @Override
750   public boolean isValid()
751   {
752     ensureFinalized();
753     return true;
754   }
755
756   /**
757    * Answers an iterator over the intervals in the store, with no particular
758    * ordering guaranteed. The iterator does not support the optional
759    * <code>remove</code> operation (throws
760    * <code>UnsupportedOperationException</code> if attempted).
761    */
762   @Override
763   public Iterator<T> iterator()
764   {
765     ensureFinalized();
766     return new Iterator<T>()
767     {
768
769       private int next;
770
771       @Override
772       public boolean hasNext()
773       {
774         return next < intervalCount;
775       }
776
777       @SuppressWarnings("unchecked")
778       @Override
779       public T next()
780       {
781         if (next >= intervalCount)
782         {
783           throw new NoSuchElementException();
784         }
785         return (T) intervals[next++];
786       }
787
788     };
789   }
790
791   private void linkFeatures()
792   {
793     if (intervalCount == 0)
794     {
795       return;
796     }
797     maxEnd = intervals[0].getEnd();
798     offsets[0] = IntervalI.NOT_CONTAINED;
799     if (intervalCount == 1)
800     {
801       return;
802     }
803     boolean isMonotonic = true;
804     for (int i = 1; i < intervalCount; i++)
805     {
806       IntervalI sf = intervals[i];
807       int begin = sf.getBegin();
808       int index = (begin <= maxEnd ? getContainedBy(i - 1, begin) : -1);
809         // System.out.println(sf + " is contained by "
810       // + (index < 0 ? null : starts[index]));
811
812       offsets[i] = (index < 0 ? IntervalI.NOT_CONTAINED
813               : isMonotonic ? index - i : i - index);
814       isMonotonic = (sf.getEnd() > maxEnd);
815       if (isMonotonic)
816       {
817         maxEnd = sf.getEnd();
818       }
819     }
820
821   }
822
823   @Override
824   public String prettyPrint()
825   {
826     switch (intervalCount + added)
827     {
828     case 0:
829       return "";
830     case 1:
831       return intervals[0] + "\n";
832     }
833     ensureFinalized();
834     String sep = "\t";
835     StringBuffer sb = new StringBuffer();
836     for (int i = 0; i < intervalCount; i++)
837     {
838       IntervalI range = intervals[i];
839       int index = i;
840       while ((index = index - Math.abs(offsets[index])) >= 0)
841       {
842         sb.append(sep);
843       }
844       sb.append(range.toString()).append('\n');
845     }
846     return sb.toString();
847   }
848
849   @Override
850   public synchronized boolean remove(Object o)
851   {
852     // if (o == null)
853     // {
854     // throw new NullPointerException();
855     // }
856     return (o != null && intervalCount > 0
857             && removeInterval((IntervalI) o));
858   }
859
860   /**
861    * Find the interval or return where it should go, possibly into the add
862    * buffer
863    * 
864    * @param interval
865    * @return index (nonnegative) or index where it would go (negative)
866    */
867
868   private int findInterval(IntervalI interval)
869   {
870
871     if (isSorted)
872     {
873       int pt = binaryIdentitySearch(interval, null);
874       // if (addPt == intervalCount || offsets[pt] == 0)
875       // return pt;
876       if (pt >= 0 || added == 0 || pt == -1 - intervalCount)
877       {
878         return pt;
879       }
880       pt = -1 - pt;
881         int start = interval.getBegin();
882         int end = interval.getEnd();
883
884         int match = pt;
885
886         while ((pt = offsets[pt]) != 0)
887         {
888           IntervalI iv = intervals[pt];
889           switch (compareRange(iv, start, end))
890           {
891           case -1:
892             break;
893           case 0:
894             if (iv.equalsInterval(interval))
895             {
896               return pt;
897             }
898           // fall through
899           case 1:
900           match = pt;
901             continue;
902           }
903         }
904       return -1 - match;
905     }
906     else
907     {
908       int i = intervalCount;
909       while (--i >= 0 && !intervals[i].equalsInterval(interval))
910       {
911         ;
912       }
913       return i;
914     }
915   }
916
917   /**
918    * Uses a binary search to find the entry and removes it if found.
919    * 
920    * @param interval
921    * @return
922    */
923   protected boolean removeInterval(IntervalI interval)
924   {
925
926     if (!isSorted || added > 0)
927     {
928       sort();
929     }
930     int i = binaryIdentitySearch(interval, bsDeleted);
931     if (i < 0)
932     {
933       return false;
934     }
935     if (deleted == 0)
936     {
937       if (bsDeleted == null)
938       {
939         bsDeleted = new BitSet(intervalCount);
940       }
941       else
942       {
943         bsDeleted.clear();
944       }
945     }
946     bsDeleted.set(i);
947     deleted++;
948     return (isTainted = true);
949   }
950
951   private void finalizeDeletion()
952   {
953     if (deleted == 0)
954     {
955       return;
956     }
957
958     // ......xxx.....xxxx.....xxxxx....
959     // ......^i,pt
960     // ...... .......
961     // ............
962     for (int i = bsDeleted.nextSetBit(0), pt = i; i >= 0;)
963     {
964       i = bsDeleted.nextClearBit(i + 1);
965       int pt1 = bsDeleted.nextSetBit(i + 1);
966       if (pt1 < 0)
967       {
968         pt1 = intervalCount;
969       }
970       int n = pt1 - i;
971       System.arraycopy(intervals, i, intervals, pt, n);
972       pt += n;
973       if (pt1 == intervalCount)
974       {
975         for (i = pt1; --i >= pt;)
976         {
977           intervals[i] = null;
978         }
979         intervalCount -= deleted;
980         deleted = 0;
981         bsDeleted.clear();
982         break;
983       }
984       i = pt1;
985     }
986
987
988   }
989
990   @Override
991   public boolean revalidate()
992   {
993     isTainted = true;
994     isSorted = false;
995     ensureFinalized();
996     return true;
997   }
998
999   @Override
1000   public int size()
1001   {
1002     return intervalCount + added - deleted;
1003   }
1004
1005   @Override
1006   public Object[] toArray()
1007   {
1008     ensureFinalized();
1009     return super.toArray();
1010   }
1011
1012   /**
1013    * Sort intervals by start (lowest first) and end (highest first).
1014    */
1015   private void sort()
1016   {
1017     if (added > 0)
1018     {
1019       intervals = finalizeAddition(new IntervalI[intervalCount + added]);
1020     }
1021     else if (deleted > 0)
1022     {
1023       finalizeDeletion();
1024     }
1025     else
1026     {
1027       Arrays.sort(intervals, 0, intervalCount, icompare);
1028     }
1029     updateMinMaxStart();
1030     isSorted = true;
1031   }
1032
1033   private void updateMinMaxStart()
1034   {
1035     if (intervalCount > 0)
1036     {
1037       minStart = intervals[0].getBegin();
1038       maxStart = intervals[intervalCount - 1].getBegin();
1039     }
1040     else
1041     {
1042       minStart = Integer.MAX_VALUE;
1043       maxStart = Integer.MIN_VALUE;
1044     }
1045   }
1046
1047   @Override
1048   public String toString()
1049   {
1050     return prettyPrint();
1051   }
1052
1053 }