JAL-3397 final update
[jalview.git] / src / intervalstore / nonc / IntervalStore0.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 IntervalStore0<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, long to, int[] ret)
84   {
85     int start = 0, start2 = 0;
86     int matched = 0;
87     int end = intervalCount - 1, end2 = intervalCount;
88     int mid, begin;
89     IntervalI e;
90     while (start <= end)
91     {
92       mid = (start + end) >>> 1;
93       e = intervals[mid];
94       begin = e.getBegin();
95       switch (Long.signum(begin - from))
96       {
97       case -1:
98         matched = mid;
99         start = mid + 1;
100         break;
101       case 0:
102       case 1:
103         end = mid - 1;
104         if (begin > to)
105         {
106           end2 = mid;
107         }
108         else
109         {
110           start2 = mid;
111         }
112         break;
113       }
114     }
115     ret[0] = end2;
116     start = Math.max(start2, end);
117     end = end2 - 1;
118
119     while (start <= end)
120     {
121       mid = (start + end) >>> 1;
122       e = intervals[mid];
123       begin = e.getBegin();
124       if (begin > to)
125       {
126         ret[0] = mid;
127         end = mid - 1;
128       }
129       else
130       {
131         start = mid + 1;
132       }
133
134     }
135     return matched;
136   }
137
138   /**
139    * My preference is for a bigendian comparison, but you may differ.
140    */
141   private Comparator<? super IntervalI> icompare;
142
143   /**
144    * bigendian is what NCList does; change icompare to switch to that
145    */
146   private boolean bigendian;
147
148   private final boolean DO_PRESORT;
149
150   private boolean isSorted;
151
152   private int minStart = Integer.MAX_VALUE, maxStart = Integer.MIN_VALUE,
153           maxEnd = Integer.MAX_VALUE;
154
155   // private Comparator<IntervalI> icompare = new IntervalComparator();
156
157   private boolean isTainted;
158
159   private int capacity = 8;
160
161   protected IntervalI[] intervals = new IntervalI[capacity];
162
163   private int[] offsets;
164
165   private int[] ret = new int[1];
166
167   protected int intervalCount;
168
169   private int added;
170
171   private int deleted;
172
173   private BitSet bsDeleted;
174
175   /**
176    * Constructor
177    */
178   public IntervalStore0()
179   {
180     this(true);
181   }
182
183   public IntervalStore0(boolean presort)
184   {
185     this(null, presort);
186   }
187
188   /**
189    * Constructor given a list of intervals. Note that the list may get sorted as
190    * a side-effect of calling this constructor.
191    */
192   public IntervalStore0(List<T> intervals)
193   {
194     this(intervals, true);
195   }
196
197   /**
198    * Allows a presort option, which can speed up initial loading of individual
199    * features but will delay the first findOverlap if set to true.
200    * 
201    * @param intervals
202    * @param presort
203    */
204   public IntervalStore0(List<T> intervals, boolean presort)
205   {
206     this(intervals, presort, null, false);
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-30] before [10-20]
221    */
222   public IntervalStore0(List<T> intervals, boolean presort,
223           Comparator<? super IntervalI> comparator, boolean bigendian)
224   {
225     if (intervals != null)
226     {
227       intervals.toArray(
228               this.intervals = new IntervalI[capacity = intervalCount = intervals
229                       .size()]);
230     }
231     DO_PRESORT = presort;
232     icompare = (comparator != null ? comparator
233             : bigendian ? IntervalI.COMPARATOR_BIGENDIAN
234                     : IntervalI.COMPARATOR_LITTLEENDIAN);
235     this.bigendian = bigendian;
236
237     if (DO_PRESORT && intervalCount > 1)
238     {
239       sort();
240     }
241     isSorted = DO_PRESORT;
242     isTainted = true;
243   }
244
245   /**
246    * Adds one interval to the store, allowing duplicates.
247    * 
248    * @param interval
249    */
250   @Override
251   public boolean add(T interval)
252   {
253     return add(interval, true);
254   }
255
256   /**
257    * Adds one interval to the store, optionally checking for duplicates.
258    * 
259    * @param interval
260    * @param allowDuplicates
261    */
262   @Override
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   /**
411    * for remove() and contains()
412    * 
413    * @param list
414    * @param interval
415    * @param bsIgnore
416    *          for deleted
417    * @return index or, if not found, -1 - "would be here"
418    */
419   public int binaryIdentitySearch(IntervalI interval, BitSet bsIgnore)
420   {
421     int start = 0;
422     int r0 = interval.getBegin();
423     int r1 = interval.getEnd();
424     int end = intervalCount - 1;
425     if (end < 0 || r0 < minStart)
426     {
427       return -1;
428     }
429     if (r0 > maxStart)
430     {
431       return -1 - intervalCount;
432     }
433     while (start <= end)
434     {
435       int mid = (start + end) >>> 1;
436       IntervalI r = intervals[mid];
437       switch (compareRange(r, r0, r1))
438       {
439       case -1:
440         start = mid + 1;
441         continue;
442       case 1:
443         end = mid - 1;
444         continue;
445       case 0:
446         IntervalI iv = intervals[mid];
447         if ((bsIgnore == null || !bsIgnore.get(mid))
448                 && iv.equalsInterval(interval))
449         {
450           return mid;
451           // found one; just scan up and down now, first checking the range, but
452           // also checking other possible aspects of equivalence.
453         }
454
455         for (int i = mid; ++i <= end;)
456         {
457           if ((iv = intervals[i]).getBegin() != r0 || iv.getEnd() != r1)
458           {
459             break;
460           }
461           if ((bsIgnore == null || !bsIgnore.get(i))
462                   && iv.equalsInterval(interval))
463           {
464             return i;
465           }
466         }
467         for (int i = mid; --i >= start;)
468         {
469           if ((iv = intervals[i]).getBegin() != r0 || iv.getEnd() < r1)
470           {
471             return -1 - ++i;
472           }
473           if ((bsIgnore == null || !bsIgnore.get(i))
474                   && iv.equalsInterval(interval))
475           {
476             return i;
477           }
478         }
479         return -1 - start;
480       }
481     }
482     return -1 - start;
483   }
484
485   // private int binaryInsertionSearch(long from, long to)
486   // {
487   // int matched = intervalCount;
488   // int end = matched - 1;
489   // int start = matched;
490   // if (end < 0 || from > intervals[end].getEnd()
491   // || from < intervals[start = 0].getBegin())
492   // return start;
493   // while (start <= end)
494   // {
495   // int mid = (start + end) >>> 1;
496   // switch (compareRange(intervals[mid], from, to))
497   // {
498   // case 0:
499   // return mid;
500   // case 1:
501   // matched = mid;
502   // end = mid - 1;
503   // continue;
504   // case -1:
505   // start = mid + 1;
506   // continue;
507   // }
508   //
509   // }
510   // return matched;
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   /**
525    * Compare an interval t to a from/to range for insertion purposes
526    * 
527    * @param t
528    * @param from
529    * @param to
530    * @return 0 if same, 1 if start is after from, or start equals from and
531    *         [bigendian: end is before to | littleendian: end is after to], else
532    *         -1
533    */
534   private int compareRange(IntervalI t, long from, long to)
535   {
536     int order = Long.signum(t.getBegin() - from);
537     return (order == 0
538             ? Long.signum(bigendian ? to - t.getEnd() : t.getEnd() - to)
539             : order);
540   }
541
542   @Override
543   public boolean contains(Object entry)
544   {
545     if (entry == null || intervalCount == 0)
546     {
547       return false;
548     }
549     if (!isSorted || deleted > 0)
550     {
551       sort();
552     }
553     return (findInterval((IntervalI) entry) >= 0);
554   }
555
556   public boolean containsInterval(IntervalI outer, IntervalI inner)
557   {
558     ensureFinalized();
559     int index = binaryIdentitySearch(inner, null);
560     if (index >= 0)
561     {
562       while ((index = index - Math.abs(offsets[index])) >= 0)
563       {
564         if (intervals[index] == outer)
565         {
566           return true;
567         }
568       }
569     }
570     return false;
571   }
572
573   private void ensureFinalized()
574   {
575     if (isTainted)
576     {
577       if (!isSorted || added > 0 || deleted > 0)
578       {
579         sort();
580       }
581       if (offsets == null || offsets.length < intervalCount)
582       {
583         offsets = new int[intervalCount];
584       }
585       linkFeatures();
586       isTainted = false;
587     }
588   }
589
590   /**
591    * Find all overlaps within the given range, inclusively.
592    * 
593    * @return a list sorted in ascending order of start position
594    * 
595    */
596   @Override
597   public List<T> findOverlaps(long from, long to)
598   {
599     List<T> list = findOverlaps(from, to, null);
600     Collections.reverse(list);
601     return list;
602   }
603
604   /**
605    * Find all overlaps within the given range, inclusively.
606    * 
607    * @return a list sorted in descending order of start position
608    * 
609    */
610
611   @SuppressWarnings("unchecked")
612   @Override
613   public List<T> findOverlaps(long from, long to, List<T> result)
614   {
615     if (result == null)
616     {
617       result = new ArrayList<>();
618     }
619     switch (intervalCount + added)
620     {
621     case 0:
622       return result;
623     case 1:
624       IntervalI sf = intervals[0];
625       if (sf.getBegin() <= to && sf.getEnd() >= from)
626       {
627         result.add((T) sf);
628       }
629       return result;
630     }
631
632     ensureFinalized();
633
634     if (from > maxEnd || to < minStart)
635     {
636       return result;
637     }
638     int index = binaryLastIntervalSearch(from, to, ret);
639     int index1 = ret[0];
640     if (index1 < 0)
641     {
642       return result;
643     }
644
645     if (index1 > index + 1)
646     {
647       while (--index1 > index)
648       {
649         result.add((T) intervals[index1]);
650       }
651     }
652     boolean isMonotonic = false;
653     while (index >= 0)
654     {
655       IntervalI sf = intervals[index];
656       if (sf.getEnd() >= from)
657       {
658         result.add((T) sf);
659       }
660       else if (isMonotonic)
661       {
662         break;
663       }
664       int offset = offsets[index];
665       isMonotonic = (offset < 0);
666       index -= (isMonotonic ? -offset : offset);
667     }
668     return result;
669   }
670
671   @Override
672   public IntervalI get(int i)
673   {
674     if (i < 0 || i >= intervalCount + added)
675     {
676       return null;
677     }
678     ensureFinalized();
679     return intervals[i];
680   }
681
682   private int getContainedBy(int index, int begin)
683   {
684     while (index >= 0)
685     {
686       IntervalI sf = intervals[index];
687       if (sf.getEnd() >= begin)
688       {
689         // System.out.println("\nIS found " + sf0.getIndex1() + ":" + sf0
690         // + "\nFS in " + sf.getIndex1() + ":" + sf);
691         return index;
692       }
693       index -= Math.abs(offsets[index]);
694     }
695     return IntervalI.NOT_CONTAINED;
696   }
697
698   @Override
699   public int getDepth()
700   {
701     ensureFinalized();
702     if (intervalCount < 2)
703     {
704       return intervalCount;
705     }
706     int maxDepth = 1;
707     IntervalI root = null;
708     for (int i = 0; i < intervalCount; i++)
709     {
710       IntervalI element = intervals[i];
711       if (offsets[i] == IntervalI.NOT_CONTAINED)
712       {
713         root = element;
714       }
715       int depth = 1;
716       int index = i;
717       int offset;
718       while ((index = index - Math.abs(offset = offsets[index])) >= 0)
719       {
720         element = intervals[index];
721         if (++depth > maxDepth && (element == root || offset < 0))
722         {
723           maxDepth = depth;
724           break;
725         }
726       }
727     }
728     return maxDepth;
729   }
730
731   @Override
732   public int getWidth()
733   {
734     ensureFinalized();
735     int w = 0;
736     for (int i = offsets.length; --i >= 0;)
737     {
738       if (offsets[i] > 0)
739       {
740         w++;
741       }
742     }
743     return w;
744   }
745
746   @Override
747   public boolean isValid()
748   {
749     ensureFinalized();
750     return true;
751   }
752
753   /**
754    * Answers an iterator over the intervals in the store, with no particular
755    * ordering guaranteed. The iterator does not support the optional
756    * <code>remove</code> operation (throws
757    * <code>UnsupportedOperationException</code> if attempted).
758    */
759   @Override
760   public Iterator<T> iterator()
761   {
762     ensureFinalized();
763     return new Iterator<T>()
764     {
765
766       private int next;
767
768       @Override
769       public boolean hasNext()
770       {
771         return next < intervalCount;
772       }
773
774       @SuppressWarnings("unchecked")
775       @Override
776       public T next()
777       {
778         if (next >= intervalCount)
779         {
780           throw new NoSuchElementException();
781         }
782         return (T) intervals[next++];
783       }
784
785     };
786   }
787
788   private void linkFeatures()
789   {
790     if (intervalCount == 0)
791     {
792       return;
793     }
794     maxEnd = intervals[0].getEnd();
795     offsets[0] = IntervalI.NOT_CONTAINED;
796     if (intervalCount == 1)
797     {
798       return;
799     }
800     boolean isMonotonic = true;
801     for (int i = 1; i < intervalCount; i++)
802     {
803       IntervalI sf = intervals[i];
804       int begin = sf.getBegin();
805       int index = (begin <= maxEnd ? getContainedBy(i - 1, begin) : -1);
806       // System.out.println(sf + " is contained by "
807       // + (index < 0 ? null : starts[index]));
808
809       offsets[i] = (index < 0 ? IntervalI.NOT_CONTAINED
810               : isMonotonic ? index - i : i - index);
811       isMonotonic = (sf.getEnd() > maxEnd);
812       if (isMonotonic)
813       {
814         maxEnd = sf.getEnd();
815       }
816     }
817
818   }
819
820   @Override
821   public String prettyPrint()
822   {
823     switch (intervalCount + added)
824     {
825     case 0:
826       return "";
827     case 1:
828       return intervals[0] + "\n";
829     }
830     ensureFinalized();
831     String sep = "\t";
832     StringBuffer sb = new StringBuffer();
833     for (int i = 0; i < intervalCount; i++)
834     {
835       IntervalI range = intervals[i];
836       int index = i;
837       while ((index = index - Math.abs(offsets[index])) >= 0)
838       {
839         sb.append(sep);
840       }
841       sb.append(range.toString()).append('\n');
842     }
843     return sb.toString();
844   }
845
846   @Override
847   public synchronized boolean remove(Object o)
848   {
849     // if (o == null)
850     // {
851     // throw new NullPointerException();
852     // }
853     return (o != null && intervalCount > 0
854             && removeInterval((IntervalI) o));
855   }
856
857   /**
858    * Find the interval or return where it should go, possibly into the add
859    * buffer
860    * 
861    * @param interval
862    * @return index (nonnegative) or index where it would go (negative)
863    */
864
865   private int findInterval(IntervalI interval)
866   {
867
868     if (isSorted)
869     {
870       int pt = binaryIdentitySearch(interval, null);
871       // if (addPt == intervalCount || offsets[pt] == 0)
872       // return pt;
873       if (pt >= 0 || added == 0 || pt == -1 - intervalCount)
874       {
875         return pt;
876       }
877       pt = -1 - pt;
878       int start = interval.getBegin();
879       int end = interval.getEnd();
880
881       int match = pt;
882
883       while ((pt = offsets[pt]) != 0)
884       {
885         IntervalI iv = intervals[pt];
886         switch (compareRange(iv, start, end))
887         {
888         case -1:
889           break;
890         case 0:
891           if (iv.equalsInterval(interval))
892           {
893             return pt;
894           }
895           // fall through
896         case 1:
897           match = pt;
898           continue;
899         }
900       }
901       return -1 - match;
902     }
903     else
904     {
905       int i = intervalCount;
906       while (--i >= 0 && !intervals[i].equalsInterval(interval))
907       {
908         ;
909       }
910       return i;
911     }
912   }
913
914   /**
915    * Uses a binary search to find the entry and removes it if found.
916    * 
917    * @param interval
918    * @return
919    */
920   protected boolean removeInterval(IntervalI interval)
921   {
922
923     if (!isSorted || added > 0)
924     {
925       sort();
926     }
927     int i = binaryIdentitySearch(interval, bsDeleted);
928     if (i < 0)
929     {
930       return false;
931     }
932     if (deleted == 0)
933     {
934       if (bsDeleted == null)
935       {
936         bsDeleted = new BitSet(intervalCount);
937       }
938       else
939       {
940         bsDeleted.clear();
941       }
942     }
943     bsDeleted.set(i);
944     deleted++;
945     return (isTainted = true);
946   }
947
948   private void finalizeDeletion()
949   {
950     if (deleted == 0)
951     {
952       return;
953     }
954
955     // ......xxx.....xxxx.....xxxxx....
956     // ......^i,pt
957     // ...... .......
958     // ............
959     for (int i = bsDeleted.nextSetBit(0), pt = i; i >= 0;)
960     {
961       i = bsDeleted.nextClearBit(i + 1);
962       int pt1 = bsDeleted.nextSetBit(i + 1);
963       if (pt1 < 0)
964       {
965         pt1 = intervalCount;
966       }
967       int n = pt1 - i;
968       System.arraycopy(intervals, i, intervals, pt, n);
969       pt += n;
970       if (pt1 == intervalCount)
971       {
972         for (i = pt1; --i >= pt;)
973         {
974           intervals[i] = null;
975         }
976         intervalCount -= deleted;
977         deleted = 0;
978         bsDeleted.clear();
979         break;
980       }
981       i = pt1;
982     }
983
984   }
985
986   @Override
987   public boolean revalidate()
988   {
989     isTainted = true;
990     isSorted = false;
991     ensureFinalized();
992     return true;
993   }
994
995   @Override
996   public int size()
997   {
998     return intervalCount + added - deleted;
999   }
1000
1001   @Override
1002   public Object[] toArray()
1003   {
1004     ensureFinalized();
1005     return super.toArray();
1006   }
1007
1008   /**
1009    * Sort intervals by start (lowest first) and end (highest first).
1010    */
1011   private void sort()
1012   {
1013     if (added > 0)
1014     {
1015       intervals = finalizeAddition(new IntervalI[intervalCount + added]);
1016     }
1017     else if (deleted > 0)
1018     {
1019       finalizeDeletion();
1020     }
1021     else
1022     {
1023       Arrays.sort(intervals, 0, intervalCount, icompare);
1024     }
1025     updateMinMaxStart();
1026     isSorted = true;
1027   }
1028
1029   private void updateMinMaxStart()
1030   {
1031     if (intervalCount > 0)
1032     {
1033       minStart = intervals[0].getBegin();
1034       maxStart = intervals[intervalCount - 1].getBegin();
1035     }
1036     else
1037     {
1038       minStart = Integer.MAX_VALUE;
1039       maxStart = Integer.MIN_VALUE;
1040     }
1041   }
1042
1043   @Override
1044   public String toString()
1045   {
1046     return prettyPrint();
1047   }
1048
1049   @Override
1050   public boolean canCheckForDuplicates()
1051   {
1052     return true;
1053   }
1054
1055 }