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