JAL-2591 simplifying hidden columns usage
[jalview.git] / src / jalview / datamodel / HiddenColumns.java
1 package jalview.datamodel;
2
3 import jalview.util.Comparison;
4 import jalview.util.ShiftList;
5
6 import java.util.ArrayList;
7 import java.util.BitSet;
8 import java.util.Collections;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Vector;
12 import java.util.concurrent.locks.ReentrantReadWriteLock;
13
14 public class HiddenColumns implements Iterable<int[]>
15 {
16   private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
17   
18   /*
19    * list of hidden column [start, end] ranges; the list is maintained in
20    * ascending start column order
21    */
22   private Vector<int[]> hiddenColumns;
23
24   /**
25    * This Method is used to return all the HiddenColumn regions
26    * 
27    * @return empty list or List of hidden column intervals
28    */
29   public List<int[]> getHiddenRegions()
30   {
31     return hiddenColumns == null ? Collections.<int[]> emptyList()
32             : hiddenColumns;
33   }
34
35   /**
36    * Find the number of hidden columns
37    * 
38    * @return number of hidden columns
39    */
40   public int getSize()
41   {
42     try
43     {
44       lock.readLock().lock();
45       int size = 0;
46       if (hasHidden())
47       {
48         for (int[] range : hiddenColumns)
49         {
50           size += range[1] - range[0] + 1;
51         }
52       }
53       return size;
54     }
55     finally
56     {
57       lock.readLock().unlock();
58     }
59   }
60
61   /**
62    * Answers if there are any hidden columns
63    * 
64    * @return true if there are hidden columns
65    */
66   public boolean hasHidden()
67   {
68     try
69     {
70       lock.readLock().lock();
71       return (hiddenColumns != null) && (!hiddenColumns.isEmpty());
72     } finally
73     {
74       lock.readLock().unlock();
75     }
76
77   }
78
79   @Override
80   public boolean equals(Object obj)
81   {
82     try
83     {
84       lock.readLock().lock();
85
86       if (!(obj instanceof HiddenColumns))
87       {
88         return false;
89       }
90       HiddenColumns that = (HiddenColumns) obj;
91
92       /*
93        * check hidden columns are either both null, or match
94        */
95       if (this.hiddenColumns == null)
96       {
97         return (that.hiddenColumns == null);
98       }
99       if (that.hiddenColumns == null
100               || that.hiddenColumns.size() != this.hiddenColumns.size())
101       {
102         return false;
103       }
104       int i = 0;
105       for (int[] thisRange : hiddenColumns)
106       {
107         int[] thatRange = that.hiddenColumns.get(i++);
108         if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
109         {
110           return false;
111         }
112       }
113       return true;
114     } finally
115     {
116       lock.readLock().unlock();
117     }
118   }
119
120   /**
121    * Return absolute column index for a visible column index
122    * 
123    * @param column
124    *          int column index in alignment view (count from zero)
125    * @return alignment column index for column
126    */
127   public int adjustForHiddenColumns(int column)
128   {
129     try
130     {
131       lock.readLock().lock();
132       int result = column;
133       if (hiddenColumns != null)
134       {
135         for (int i = 0; i < hiddenColumns.size(); i++)
136         {
137           int[] region = hiddenColumns.elementAt(i);
138           if (result >= region[0])
139           {
140             result += region[1] - region[0] + 1;
141           }
142         }
143       }
144       return result;
145     } finally
146     {
147       lock.readLock().unlock();
148     }
149   }
150
151   /**
152    * Use this method to find out where a column will appear in the visible
153    * alignment when hidden columns exist. If the column is not visible, then the
154    * left-most visible column will always be returned.
155    * 
156    * @param hiddenColumn
157    *          the column index in the full alignment including hidden columns
158    * @return the position of the column in the visible alignment
159    */
160   public int findColumnPosition(int hiddenColumn)
161   {
162     try
163     {
164       lock.readLock().lock();
165       int result = hiddenColumn;
166       if (hiddenColumns != null)
167       {
168         int index = 0;
169         int[] region;
170         do
171         {
172           region = hiddenColumns.elementAt(index++);
173           if (hiddenColumn > region[1])
174           {
175             result -= region[1] + 1 - region[0];
176           }
177         } while ((hiddenColumn > region[1])
178                 && (index < hiddenColumns.size()));
179
180         if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
181         {
182           // Here the hidden column is within a region, so
183           // we want to return the position of region[0]-1, adjusted for any
184           // earlier hidden columns.
185           // Calculate the difference between the actual hidden col position
186           // and region[0]-1, and then subtract from result to convert result
187           // from
188           // the adjusted hiddenColumn value to the adjusted region[0]-1 value
189
190           // However, if the region begins at 0 we cannot return region[0]-1
191           // just return 0
192           if (region[0] == 0)
193           {
194             return 0;
195           }
196           else
197           {
198             return result - (hiddenColumn - region[0] + 1);
199           }
200         }
201       }
202       return result; // return the shifted position after removing hidden
203                      // columns.
204     } finally
205     {
206       lock.readLock().unlock();
207     }
208   }
209
210   /**
211    * Find the visible column which is a given visible number of columns to the
212    * left of another visible column. i.e. for a startColumn x, the column which
213    * is distance 1 away will be column x-1.
214    * 
215    * @param visibleDistance
216    *          the number of visible columns to offset by
217    * @param startColumn
218    *          the column to start from
219    * @return the position of the column in the visible alignment
220    */
221   public int subtractVisibleColumns(int visibleDistance, int startColumn)
222   {
223     try
224     {
225
226       lock.readLock().lock();
227     int distance = visibleDistance;
228
229     // in case startColumn is in a hidden region, move it to the left
230     int start = adjustForHiddenColumns(findColumnPosition(startColumn));
231
232     // get index of hidden region to left of start
233     int index = getHiddenIndexLeft(start);
234     if (index == -1)
235     {
236       // no hidden regions to left of startColumn
237       return start - distance;
238     }
239
240     // walk backwards through the alignment subtracting the counts of visible
241     // columns from distance
242     int[] region;
243     int gap = 0;
244     int nextstart = start;
245
246     while ((index > -1) && (distance - gap > 0))
247     {
248       // subtract the gap to right of region from distance
249       distance -= gap;
250       start = nextstart;
251
252       // calculate the next gap
253       region = hiddenColumns.get(index);
254       gap = start - region[1];
255
256       // set start to just to left of current region
257       nextstart = region[0] - 1;
258       index--;
259     }
260
261     if (distance - gap > 0)
262     {
263       // fell out of loop because there are no more hidden regions
264       distance -= gap;
265       return nextstart - distance;
266     }
267     return start - distance;
268     } finally
269     {
270       lock.readLock().unlock();
271     }
272
273   }
274
275   /**
276    * Use this method to determine where the next hiddenRegion starts
277    * 
278    * @param hiddenRegion
279    *          index of hidden region (counts from 0)
280    * @return column number in visible view
281    */
282   public int findHiddenRegionPosition(int hiddenRegion)
283   {
284     try
285     {
286       lock.readLock().lock();
287       int result = 0;
288       if (hiddenColumns != null)
289       {
290         int index = 0;
291         int gaps = 0;
292         do
293         {
294           int[] region = hiddenColumns.elementAt(index);
295           if (hiddenRegion == 0)
296           {
297             return region[0];
298           }
299
300           gaps += region[1] + 1 - region[0];
301           result = region[1] + 1;
302           index++;
303         } while (index <= hiddenRegion);
304
305         result -= gaps;
306       }
307
308       return result;
309     }
310     finally
311     {
312       lock.readLock().unlock();
313     }
314   }
315
316   /**
317    * This method returns the rightmost limit of a region of an alignment with
318    * hidden columns. In otherwords, the next hidden column.
319    * 
320    * @param index
321    *          int
322    */
323   public int getHiddenBoundaryRight(int alPos)
324   {
325     try
326     {
327       lock.readLock().lock();
328       if (hiddenColumns != null)
329       {
330         int index = 0;
331         do
332         {
333           int[] region = hiddenColumns.elementAt(index);
334           if (alPos < region[0])
335           {
336             return region[0];
337           }
338
339           index++;
340         } while (index < hiddenColumns.size());
341       }
342
343       return alPos;
344     } finally
345     {
346       lock.readLock().unlock();
347     }
348
349   }
350
351   /**
352    * This method returns the leftmost limit of a region of an alignment with
353    * hidden columns. In otherwords, the previous hidden column.
354    * 
355    * @param index
356    *          int
357    */
358   public int getHiddenBoundaryLeft(int alPos)
359   {
360     try
361     {
362       lock.readLock().lock();
363
364     if (hiddenColumns != null)
365     {
366       int index = hiddenColumns.size() - 1;
367       do
368       {
369         int[] region = hiddenColumns.elementAt(index);
370         if (alPos > region[1])
371         {
372           return region[1];
373         }
374
375         index--;
376       } while (index > -1);
377     }
378
379     return alPos;
380     } finally
381     {
382       lock.readLock().unlock();
383     }
384   }
385
386   /**
387    * This method returns the index of the hidden region to the left of a column
388    * position. If the column is in a hidden region it returns the index of the
389    * region to the left. If there is no hidden region to the left it returns -1.
390    * 
391    * @param pos
392    *          int
393    */
394   private int getHiddenIndexLeft(int pos)
395   {
396     try
397     {
398
399       lock.readLock().lock();
400     if (hiddenColumns != null)
401     {
402       int index = hiddenColumns.size() - 1;
403       do
404       {
405         int[] region = hiddenColumns.elementAt(index);
406         if (pos > region[1])
407         {
408           return index;
409         }
410
411         index--;
412       } while (index > -1);
413     }
414
415     return -1;
416     } finally
417     {
418       lock.readLock().unlock();
419     }
420
421   }
422
423   /**
424    * Adds the specified column range to the hidden columns
425    * 
426    * @param start
427    * @param end
428    */
429   public void hideColumns(int start, int end)
430   {
431     hideColumns(start, end, false);
432   }
433
434   /**
435    * Adds the specified column range to the hidden columns
436    * 
437    * @param start
438    * @param end
439    */
440   private void hideColumns(int start, int end, boolean alreadyLocked)
441   {
442     try
443     {
444
445       if (!alreadyLocked)
446       {
447         lock.writeLock().lock();
448       }
449
450     if (hiddenColumns == null)
451     {
452       hiddenColumns = new Vector<>();
453     }
454
455     /*
456      * traverse existing hidden ranges and insert / amend / append as
457      * appropriate
458      */
459     for (int i = 0; i < hiddenColumns.size(); i++)
460     {
461       int[] region = hiddenColumns.elementAt(i);
462
463       if (end < region[0] - 1)
464       {
465         /*
466          * insert discontiguous preceding range
467          */
468         hiddenColumns.insertElementAt(new int[] { start, end }, i);
469         return;
470       }
471
472       if (end <= region[1])
473       {
474         /*
475          * new range overlaps existing, or is contiguous preceding it - adjust
476          * start column
477          */
478         region[0] = Math.min(region[0], start);
479         return;
480       }
481
482       if (start <= region[1] + 1)
483       {
484         /*
485          * new range overlaps existing, or is contiguous following it - adjust
486          * start and end columns
487          */
488         region[0] = Math.min(region[0], start);
489         region[1] = Math.max(region[1], end);
490
491         /*
492          * also update or remove any subsequent ranges 
493          * that are overlapped
494          */
495         while (i < hiddenColumns.size() - 1)
496         {
497           int[] nextRegion = hiddenColumns.get(i + 1);
498           if (nextRegion[0] > end + 1)
499           {
500             /*
501              * gap to next hidden range - no more to update
502              */
503             break;
504           }
505           region[1] = Math.max(nextRegion[1], end);
506           hiddenColumns.remove(i + 1);
507         }
508         return;
509       }
510     }
511
512     /*
513      * remaining case is that the new range follows everything else
514      */
515     hiddenColumns.addElement(new int[] { start, end });
516     } finally
517     {
518       if (!alreadyLocked)
519       {
520         lock.writeLock().unlock();
521       }
522     }
523   }
524
525   public boolean isVisible(int column)
526   {
527     try
528     {
529       lock.readLock().lock();
530
531     if (hiddenColumns != null)
532     {
533       for (int[] region : hiddenColumns)
534       {
535         if (column >= region[0] && column <= region[1])
536         {
537           return false;
538         }
539       }
540     }
541
542     return true;
543     } finally
544     {
545       lock.readLock().unlock();
546     }
547   }
548
549   /**
550    * ColumnSelection
551    */
552   public HiddenColumns()
553   {
554   }
555
556   /**
557    * Copy constructor
558    * 
559    * @param copy
560    */
561   public HiddenColumns(HiddenColumns copy)
562   {
563     try
564     {
565
566       lock.readLock().lock();
567       if (copy != null)
568       {
569         if (copy.hiddenColumns != null)
570         {
571           hiddenColumns = copy.copyHiddenRegions();
572         }
573       }
574     }
575     finally
576     {
577       lock.readLock().unlock();
578     }
579   }
580
581   private Vector<int[]> copyHiddenRegions()
582   {
583     Vector<int[]> copy = new Vector<>(hiddenColumns.size());
584     for (int i = 0, j = hiddenColumns.size(); i < j; i++)
585     {
586       int[] rh, cp;
587       rh = hiddenColumns.elementAt(i);
588       if (rh != null)
589       {
590         cp = new int[rh.length];
591         System.arraycopy(rh, 0, cp, 0, rh.length);
592         copy.addElement(cp);
593       }
594     }
595     return copy;
596   }
597
598   private ArrayList<int[]> copyHiddenRegionsToArrayList()
599   {
600     ArrayList<int[]> copy = new ArrayList<>(hiddenColumns.size());
601     for (int i = 0, j = hiddenColumns.size(); i < j; i++)
602     {
603       int[] rh, cp;
604       rh = hiddenColumns.elementAt(i);
605       if (rh != null)
606       {
607         cp = new int[rh.length];
608         System.arraycopy(rh, 0, cp, 0, rh.length);
609         copy.add(cp);
610       }
611     }
612     return copy;
613   }
614
615   public void getHiddenColumnsCopy(Vector<int[]> copy)
616   {
617     try
618     {
619       lock.readLock().lock();
620       copy = copyHiddenRegions();
621     } finally
622     {
623       lock.readLock().unlock();
624     }
625   }
626
627   public void getHiddenColumnsCopy(ArrayList<int[]> copy)
628   {
629     try
630     {
631       lock.readLock().lock();
632       copy = copyHiddenRegionsToArrayList();
633     } finally
634     {
635       lock.readLock().unlock();
636     }
637   }
638
639   /**
640    * propagate shift in alignment columns to column selection
641    * 
642    * @param start
643    *          beginning of edit
644    * @param left
645    *          shift in edit (+ve for removal, or -ve for inserts)
646    */
647   public List<int[]> compensateForEdit(int start, int change,
648           ColumnSelection sel)
649   {
650     try
651     {
652       lock.writeLock().lock();
653       List<int[]> deletedHiddenColumns = null;
654
655       if (hiddenColumns != null)
656       {
657         deletedHiddenColumns = new ArrayList<>();
658         int hSize = hiddenColumns.size();
659         for (int i = 0; i < hSize; i++)
660         {
661           int[] region = hiddenColumns.elementAt(i);
662           if (region[0] > start && start + change > region[1])
663           {
664             deletedHiddenColumns.add(region);
665
666             hiddenColumns.removeElementAt(i);
667             i--;
668             hSize--;
669             continue;
670           }
671
672           if (region[0] > start)
673           {
674             region[0] -= change;
675             region[1] -= change;
676           }
677
678           if (region[0] < 0)
679           {
680             region[0] = 0;
681           }
682
683         }
684
685         this.revealHiddenColumns(0, sel);
686       }
687
688       return deletedHiddenColumns;
689     } finally
690     {
691       lock.writeLock().unlock();
692     }
693   }
694
695   /**
696    * propagate shift in alignment columns to column selection special version of
697    * compensateForEdit - allowing for edits within hidden regions
698    * 
699    * @param start
700    *          beginning of edit
701    * @param left
702    *          shift in edit (+ve for removal, or -ve for inserts)
703    */
704   public void compensateForDelEdits(int start, int change)
705   {
706     try
707     {
708       lock.writeLock().lock();
709       if (hiddenColumns != null)
710       {
711         for (int i = 0; i < hiddenColumns.size(); i++)
712         {
713           int[] region = hiddenColumns.elementAt(i);
714           if (region[0] >= start)
715           {
716             region[0] -= change;
717           }
718           if (region[1] >= start)
719           {
720             region[1] -= change;
721           }
722           if (region[1] < region[0])
723           {
724             hiddenColumns.removeElementAt(i--);
725           }
726
727           if (region[0] < 0)
728           {
729             region[0] = 0;
730           }
731           if (region[1] < 0)
732           {
733             region[1] = 0;
734           }
735         }
736       }
737     }
738     finally
739     {
740       lock.writeLock().unlock();
741     }
742   }
743
744   /**
745    * return all visible segments between the given start and end boundaries
746    * 
747    * @param start
748    *          (first column inclusive from 0)
749    * @param end
750    *          (last column - not inclusive)
751    * @return int[] {i_start, i_end, ..} where intervals lie in
752    *         start<=i_start<=i_end<end
753    */
754   public int[] getVisibleContigs(int start, int end)
755   {
756     try
757     {
758       lock.readLock().lock();
759       if (hiddenColumns != null && hiddenColumns.size() > 0)
760       {
761         List<int[]> visiblecontigs = new ArrayList<>();
762         List<int[]> regions = getHiddenRegions();
763
764         int vstart = start;
765         int[] region;
766         int hideStart, hideEnd;
767
768         for (int j = 0; vstart < end && j < regions.size(); j++)
769         {
770           region = regions.get(j);
771           hideStart = region[0];
772           hideEnd = region[1];
773
774           if (hideEnd < vstart)
775           {
776             continue;
777           }
778           if (hideStart > vstart)
779           {
780             visiblecontigs.add(new int[] { vstart, hideStart - 1 });
781           }
782           vstart = hideEnd + 1;
783         }
784
785         if (vstart < end)
786         {
787           visiblecontigs.add(new int[] { vstart, end - 1 });
788         }
789         int[] vcontigs = new int[visiblecontigs.size() * 2];
790         for (int i = 0, j = visiblecontigs.size(); i < j; i++)
791         {
792           int[] vc = visiblecontigs.get(i);
793           visiblecontigs.set(i, null);
794           vcontigs[i * 2] = vc[0];
795           vcontigs[i * 2 + 1] = vc[1];
796         }
797         visiblecontigs.clear();
798         return vcontigs;
799       }
800       else
801       {
802         return new int[] { start, end - 1 };
803       }
804     }
805     finally
806     {
807       lock.readLock().unlock();
808     }
809   }
810
811   public String[] getVisibleSequenceStrings(int start, int end,
812           SequenceI[] seqs)
813   {
814     try
815     {
816       lock.readLock().lock();
817       int i, iSize = seqs.length;
818       String selections[] = new String[iSize];
819       if (hiddenColumns != null && hiddenColumns.size() > 0)
820       {
821         for (i = 0; i < iSize; i++)
822         {
823           StringBuffer visibleSeq = new StringBuffer();
824           List<int[]> regions = getHiddenRegions();
825
826           int blockStart = start, blockEnd = end;
827           int[] region;
828           int hideStart, hideEnd;
829
830           for (int j = 0; j < regions.size(); j++)
831           {
832             region = regions.get(j);
833             hideStart = region[0];
834             hideEnd = region[1];
835
836             if (hideStart < start)
837             {
838               continue;
839             }
840
841             blockStart = Math.min(blockStart, hideEnd + 1);
842             blockEnd = Math.min(blockEnd, hideStart);
843
844             if (blockStart > blockEnd)
845             {
846               break;
847             }
848
849             visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
850
851             blockStart = hideEnd + 1;
852             blockEnd = end;
853           }
854
855           if (end > blockStart)
856           {
857             visibleSeq.append(seqs[i].getSequence(blockStart, end));
858           }
859
860           selections[i] = visibleSeq.toString();
861         }
862       }
863       else
864       {
865         for (i = 0; i < iSize; i++)
866         {
867           selections[i] = seqs[i].getSequenceAsString(start, end);
868         }
869       }
870
871       return selections;
872     }
873     finally
874     {
875       lock.readLock().unlock();
876     }
877   }
878
879   /**
880    * Locate the first and last position visible for this sequence. if seq isn't
881    * visible then return the position of the left and right of the hidden
882    * boundary region, and the corresponding alignment column indices for the
883    * extent of the sequence
884    * 
885    * @param seq
886    * @return int[] { visible start, visible end, first seqpos, last seqpos,
887    *         alignment index for seq start, alignment index for seq end }
888    */
889   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
890   {
891     try
892     {
893       lock.readLock().lock();
894       int fpos = seq.getStart(), lpos = seq.getEnd();
895       int start = 0;
896
897       if (hiddenColumns == null || hiddenColumns.size() == 0)
898       {
899         int ifpos = seq.findIndex(fpos) - 1,
900                 ilpos = seq.findIndex(lpos) - 1;
901         return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
902       }
903
904       // Simply walk along the sequence whilst watching for hidden column
905       // boundaries
906       List<int[]> regions = getHiddenRegions();
907       int spos = fpos, lastvispos = -1, rcount = 0,
908               hideStart = seq.getLength(), hideEnd = -1;
909       int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
910       boolean foundStart = false;
911       for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
912               && p < pLen; p++)
913       {
914         if (!Comparison.isGap(seq.getCharAt(p)))
915         {
916           // keep track of first/last column
917           // containing sequence data regardless of visibility
918           if (firstP == -1)
919           {
920             firstP = p;
921           }
922           lastP = p;
923           // update hidden region start/end
924           while (hideEnd < p && rcount < regions.size())
925           {
926             int[] region = regions.get(rcount++);
927             visPrev = visNext;
928             visNext += region[0] - visPrev;
929             hideStart = region[0];
930             hideEnd = region[1];
931           }
932           if (hideEnd < p)
933           {
934             hideStart = seq.getLength();
935           }
936           // update visible boundary for sequence
937           if (p < hideStart)
938           {
939             if (!foundStart)
940             {
941               fpos = spos;
942               start = p;
943               foundStart = true;
944             }
945             lastvispos = p;
946             lpos = spos;
947           }
948           // look for next sequence position
949           spos++;
950         }
951       }
952       if (foundStart)
953       {
954         return new int[] { findColumnPosition(start),
955             findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
956       }
957       // otherwise, sequence was completely hidden
958       return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
959     }
960     finally
961     {
962       lock.readLock().unlock();
963     }
964   }
965
966   /**
967    * delete any columns in alignmentAnnotation that are hidden (including
968    * sequence associated annotation).
969    * 
970    * @param alignmentAnnotation
971    */
972   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
973   {
974     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
975   }
976
977   /**
978    * delete any columns in alignmentAnnotation that are hidden (including
979    * sequence associated annotation).
980    * 
981    * @param start
982    *          remove any annotation to the right of this column
983    * @param end
984    *          remove any annotation to the left of this column
985    * @param alignmentAnnotation
986    *          the annotation to operate on
987    */
988   public void makeVisibleAnnotation(int start, int end,
989           AlignmentAnnotation alignmentAnnotation)
990   {
991     try
992     {
993       lock.readLock().lock();
994       if (alignmentAnnotation.annotations == null)
995       {
996         return;
997       }
998       if (start == end && end == -1)
999       {
1000         start = 0;
1001         end = alignmentAnnotation.annotations.length;
1002       }
1003       if (hiddenColumns != null && hiddenColumns.size() > 0)
1004       {
1005         // then mangle the alignmentAnnotation annotation array
1006         Vector<Annotation[]> annels = new Vector<>();
1007         Annotation[] els = null;
1008         List<int[]> regions = getHiddenRegions();
1009         int blockStart = start, blockEnd = end;
1010         int[] region;
1011         int hideStart, hideEnd, w = 0;
1012
1013         for (int j = 0; j < regions.size(); j++)
1014         {
1015           region = regions.get(j);
1016           hideStart = region[0];
1017           hideEnd = region[1];
1018
1019           if (hideStart < start)
1020           {
1021             continue;
1022           }
1023
1024           blockStart = Math.min(blockStart, hideEnd + 1);
1025           blockEnd = Math.min(blockEnd, hideStart);
1026
1027           if (blockStart > blockEnd)
1028           {
1029             break;
1030           }
1031
1032           annels.addElement(els = new Annotation[blockEnd - blockStart]);
1033           System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1034                   0, els.length);
1035           w += els.length;
1036           blockStart = hideEnd + 1;
1037           blockEnd = end;
1038         }
1039
1040         if (end > blockStart)
1041         {
1042           annels.addElement(els = new Annotation[end - blockStart + 1]);
1043           if ((els.length
1044                   + blockStart) <= alignmentAnnotation.annotations.length)
1045           {
1046             // copy just the visible segment of the annotation row
1047             System.arraycopy(alignmentAnnotation.annotations, blockStart,
1048                     els, 0, els.length);
1049           }
1050           else
1051           {
1052             // copy to the end of the annotation row
1053             System.arraycopy(alignmentAnnotation.annotations, blockStart,
1054                     els, 0,
1055                     (alignmentAnnotation.annotations.length - blockStart));
1056           }
1057           w += els.length;
1058         }
1059         if (w == 0)
1060         {
1061           return;
1062         }
1063
1064         alignmentAnnotation.annotations = new Annotation[w];
1065         w = 0;
1066
1067         for (Annotation[] chnk : annels)
1068         {
1069           System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1070                   chnk.length);
1071           w += chnk.length;
1072         }
1073       }
1074       else
1075       {
1076         alignmentAnnotation.restrict(start, end);
1077       }
1078     }
1079     finally
1080     {
1081       lock.readLock().unlock();
1082     }
1083   }
1084
1085   /**
1086    * 
1087    * @return true if there are columns hidden
1088    */
1089   public boolean hasHiddenColumns()
1090   {
1091     try
1092     {
1093       lock.readLock().lock();
1094       return hiddenColumns != null && hiddenColumns.size() > 0;
1095     } finally
1096     {
1097       lock.readLock().unlock();
1098     }
1099   }
1100
1101   /**
1102    * 
1103    * @return true if there are more than one set of columns hidden
1104    */
1105   public boolean hasManyHiddenColumns()
1106   {
1107     try
1108     {
1109       lock.readLock().lock();
1110       return hiddenColumns != null && hiddenColumns.size() > 1;
1111     } finally
1112     {
1113       lock.readLock().unlock();
1114     }
1115   }
1116
1117   /**
1118    * mark the columns corresponding to gap characters as hidden in the column
1119    * selection
1120    * 
1121    * @param sr
1122    */
1123   public void hideInsertionsFor(SequenceI sr)
1124   {
1125     try
1126     {
1127       lock.writeLock().lock();
1128       List<int[]> inserts = sr.getInsertions();
1129       for (int[] r : inserts)
1130       {
1131         hideColumns(r[0], r[1], true);
1132       }
1133     } finally
1134     {
1135       lock.writeLock().unlock();
1136     }
1137   }
1138
1139   /**
1140    * Unhides, and adds to the selection list, all hidden columns
1141    */
1142   public void revealAllHiddenColumns(ColumnSelection sel)
1143   {
1144     try
1145     {
1146       lock.writeLock().lock();
1147       if (hiddenColumns != null)
1148       {
1149         for (int i = 0; i < hiddenColumns.size(); i++)
1150         {
1151           int[] region = hiddenColumns.elementAt(i);
1152           for (int j = region[0]; j < region[1] + 1; j++)
1153           {
1154             sel.addElement(j);
1155           }
1156         }
1157       }
1158
1159       hiddenColumns = null;
1160     }
1161     finally
1162     {
1163       lock.writeLock().unlock();
1164     }
1165   }
1166
1167   /**
1168    * Reveals, and marks as selected, the hidden column range with the given
1169    * start column
1170    * 
1171    * @param start
1172    */
1173   public void revealHiddenColumns(int start, ColumnSelection sel)
1174   {
1175     try
1176     {
1177       lock.writeLock().lock();
1178       for (int i = 0; i < hiddenColumns.size(); i++)
1179       {
1180         int[] region = hiddenColumns.elementAt(i);
1181         if (start == region[0])
1182         {
1183           for (int j = region[0]; j < region[1] + 1; j++)
1184           {
1185             sel.addElement(j);
1186           }
1187
1188           hiddenColumns.removeElement(region);
1189           break;
1190         }
1191       }
1192       if (hiddenColumns.size() == 0)
1193       {
1194         hiddenColumns = null;
1195       }
1196     }
1197     finally
1198     {
1199       lock.writeLock().unlock();
1200     }
1201   }
1202
1203   /**
1204    * removes intersection of position,length ranges in deletions from the
1205    * start,end regions marked in intervals.
1206    * 
1207    * @param shifts
1208    * @param intervals
1209    * @return
1210    */
1211   private boolean pruneIntervalVector(final List<int[]> shifts,
1212           Vector<int[]> intervals)
1213   {
1214     boolean pruned = false;
1215     int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
1216     int hr[] = intervals.elementAt(i);
1217     int sr[] = shifts.get(s);
1218     while (i <= j && s <= t)
1219     {
1220       boolean trailinghn = hr[1] >= sr[0];
1221       if (!trailinghn)
1222       {
1223         if (i < j)
1224         {
1225           hr = intervals.elementAt(++i);
1226         }
1227         else
1228         {
1229           i++;
1230         }
1231         continue;
1232       }
1233       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
1234       if (endshift < hr[0] || endshift < sr[0])
1235       { // leadinghc disjoint or not a deletion
1236         if (s < t)
1237         {
1238           sr = shifts.get(++s);
1239         }
1240         else
1241         {
1242           s++;
1243         }
1244         continue;
1245       }
1246       boolean leadinghn = hr[0] >= sr[0];
1247       boolean leadinghc = hr[0] < endshift;
1248       boolean trailinghc = hr[1] < endshift;
1249       if (leadinghn)
1250       {
1251         if (trailinghc)
1252         { // deleted hidden region.
1253           intervals.removeElementAt(i);
1254           pruned = true;
1255           j--;
1256           if (i <= j)
1257           {
1258             hr = intervals.elementAt(i);
1259           }
1260           continue;
1261         }
1262         if (leadinghc)
1263         {
1264           hr[0] = endshift; // clip c terminal region
1265           leadinghn = !leadinghn;
1266           pruned = true;
1267         }
1268       }
1269       if (!leadinghn)
1270       {
1271         if (trailinghc)
1272         {
1273           if (trailinghn)
1274           {
1275             hr[1] = sr[0] - 1;
1276             pruned = true;
1277           }
1278         }
1279         else
1280         {
1281           // sr contained in hr
1282           if (s < t)
1283           {
1284             sr = shifts.get(++s);
1285           }
1286           else
1287           {
1288             s++;
1289           }
1290           continue;
1291         }
1292       }
1293     }
1294     return pruned; // true if any interval was removed or modified by
1295     // operations.
1296   }
1297
1298   /**
1299    * remove any hiddenColumns or selected columns and shift remaining based on a
1300    * series of position, range deletions.
1301    * 
1302    * @param deletions
1303    */
1304   public void pruneDeletions(List<int[]> shifts)
1305   {
1306     try
1307     {
1308       lock.writeLock().lock();
1309       // delete any intervals intersecting.
1310       if (hiddenColumns != null)
1311       {
1312         pruneIntervalVector(shifts, hiddenColumns);
1313         if (hiddenColumns != null && hiddenColumns.size() == 0)
1314         {
1315           hiddenColumns = null;
1316         }
1317       }
1318     }
1319     finally
1320     {
1321       lock.writeLock().unlock();
1322     }
1323   }
1324
1325   /**
1326    * Add gaps into the sequences aligned to profileseq under the given
1327    * AlignmentView
1328    * 
1329    * @param profileseq
1330    * @param al
1331    *          - alignment to have gaps inserted into it
1332    * @param input
1333    *          - alignment view where sequence corresponding to profileseq is
1334    *          first entry
1335    * @return new HiddenColumns for new alignment view, with insertions into
1336    *         profileseq marked as hidden.
1337    */
1338   public static HiddenColumns propagateInsertions(SequenceI profileseq,
1339           AlignmentI al, AlignmentView input)
1340   {
1341     int profsqpos = 0;
1342
1343     char gc = al.getGapCharacter();
1344     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
1345     HiddenColumns nview = (HiddenColumns) alandhidden[1];
1346     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
1347     nview.propagateInsertions(profileseq, al, origseq);
1348     return nview;
1349   }
1350
1351   /**
1352    * 
1353    * @param profileseq
1354    *          - sequence in al which corresponds to origseq
1355    * @param al
1356    *          - alignment which is to have gaps inserted into it
1357    * @param origseq
1358    *          - sequence corresponding to profileseq which defines gap map for
1359    *          modifying al
1360    */
1361   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
1362           SequenceI origseq)
1363   {
1364     char gc = al.getGapCharacter();
1365     // recover mapping between sequence's non-gap positions and positions
1366     // mapping to view.
1367     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1368     int[] viscontigs = al.getHiddenColumns().getVisibleContigs(0,
1369             profileseq.getLength());
1370     int spos = 0;
1371     int offset = 0;
1372
1373     // add profile to visible contigs
1374     for (int v = 0; v < viscontigs.length; v += 2)
1375     {
1376       if (viscontigs[v] > spos)
1377       {
1378         StringBuffer sb = new StringBuffer();
1379         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1380         {
1381           sb.append(gc);
1382         }
1383         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1384         {
1385           SequenceI sqobj = al.getSequenceAt(s);
1386           if (sqobj != profileseq)
1387           {
1388             String sq = al.getSequenceAt(s).getSequenceAsString();
1389             if (sq.length() <= spos + offset)
1390             {
1391               // pad sequence
1392               int diff = spos + offset - sq.length() - 1;
1393               if (diff > 0)
1394               {
1395                 // pad gaps
1396                 sq = sq + sb;
1397                 while ((diff = spos + offset - sq.length() - 1) > 0)
1398                 {
1399                   // sq = sq
1400                   // + ((diff >= sb.length()) ? sb.toString() : sb
1401                   // .substring(0, diff));
1402                   if (diff >= sb.length())
1403                   {
1404                     sq += sb.toString();
1405                   }
1406                   else
1407                   {
1408                     char[] buf = new char[diff];
1409                     sb.getChars(0, diff, buf, 0);
1410                     sq += buf.toString();
1411                   }
1412                 }
1413               }
1414               sq += sb.toString();
1415             }
1416             else
1417             {
1418               al.getSequenceAt(s).setSequence(
1419                       sq.substring(0, spos + offset) + sb.toString()
1420                               + sq.substring(spos + offset));
1421             }
1422           }
1423         }
1424         // offset+=sb.length();
1425       }
1426       spos = viscontigs[v + 1] + 1;
1427     }
1428     if ((offset + spos) < profileseq.getLength())
1429     {
1430       // pad the final region with gaps.
1431       StringBuffer sb = new StringBuffer();
1432       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1433       {
1434         sb.append(gc);
1435       }
1436       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1437       {
1438         SequenceI sqobj = al.getSequenceAt(s);
1439         if (sqobj == profileseq)
1440         {
1441           continue;
1442         }
1443         String sq = sqobj.getSequenceAsString();
1444         // pad sequence
1445         int diff = origseq.getLength() - sq.length();
1446         while (diff > 0)
1447         {
1448           // sq = sq
1449           // + ((diff >= sb.length()) ? sb.toString() : sb
1450           // .substring(0, diff));
1451           if (diff >= sb.length())
1452           {
1453             sq += sb.toString();
1454           }
1455           else
1456           {
1457             char[] buf = new char[diff];
1458             sb.getChars(0, diff, buf, 0);
1459             sq += buf.toString();
1460           }
1461           diff = origseq.getLength() - sq.length();
1462         }
1463       }
1464     }
1465   }
1466
1467   /**
1468    * remove any hiddenColumns or selected columns and shift remaining based on a
1469    * series of position, range deletions.
1470    * 
1471    * @param deletions
1472    */
1473   private void pruneDeletions(ShiftList deletions)
1474   {
1475     if (deletions != null)
1476     {
1477       final List<int[]> shifts = deletions.getShifts();
1478       if (shifts != null && shifts.size() > 0)
1479       {
1480         pruneDeletions(shifts);
1481
1482         // and shift the rest.
1483         this.compensateForEdits(deletions);
1484       }
1485     }
1486   }
1487
1488   /**
1489    * Adjust hidden column boundaries based on a series of column additions or
1490    * deletions in visible regions.
1491    * 
1492    * @param shiftrecord
1493    * @return
1494    */
1495   private ShiftList compensateForEdits(ShiftList shiftrecord)
1496   {
1497     if (shiftrecord != null)
1498     {
1499       final List<int[]> shifts = shiftrecord.getShifts();
1500       if (shifts != null && shifts.size() > 0)
1501       {
1502         int shifted = 0;
1503         for (int i = 0, j = shifts.size(); i < j; i++)
1504         {
1505           int[] sh = shifts.get(i);
1506           compensateForDelEdits(shifted + sh[0], sh[1]);
1507           shifted -= sh[1];
1508         }
1509       }
1510       return shiftrecord.getInverse();
1511     }
1512     return null;
1513   }
1514
1515   /**
1516    * Returns a hashCode built from hidden column ranges
1517    */
1518   @Override
1519   public int hashCode()
1520   {
1521     try
1522     {
1523       lock.readLock().lock();
1524       int hashCode = 1;
1525       if (hiddenColumns != null)
1526       {
1527         for (int[] hidden : hiddenColumns)
1528         {
1529           hashCode = 31 * hashCode + hidden[0];
1530           hashCode = 31 * hashCode + hidden[1];
1531         }
1532       }
1533       return hashCode;
1534     }
1535     finally
1536     {
1537       lock.readLock().unlock();
1538     }
1539   }
1540
1541   /**
1542    * Hide columns corresponding to the marked bits
1543    * 
1544    * @param inserts
1545    *          - columns map to bits starting from zero
1546    */
1547   public void hideMarkedBits(BitSet inserts)
1548   {
1549     try
1550     {
1551       lock.writeLock().lock();
1552       for (int firstSet = inserts
1553               .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
1554                       .nextSetBit(lastSet))
1555       {
1556         lastSet = inserts.nextClearBit(firstSet);
1557         hideColumns(firstSet, lastSet - 1, true);
1558       }
1559     } finally
1560     {
1561       lock.writeLock().unlock();
1562     }
1563   }
1564
1565   /**
1566    * 
1567    * @param inserts
1568    *          BitSet where hidden columns will be marked
1569    */
1570   public void markHiddenRegions(BitSet inserts)
1571   {
1572     try
1573     {
1574       lock.readLock().lock();
1575       if (hiddenColumns == null)
1576       {
1577         return;
1578       }
1579       for (int[] range : hiddenColumns)
1580       {
1581         inserts.set(range[0], range[1] + 1);
1582       }
1583     }
1584     finally
1585     {
1586       lock.readLock().unlock();
1587     }
1588   }
1589
1590   @Override
1591   public Iterator<int[]> iterator()
1592   {
1593     if (hiddenColumns == null)
1594     {
1595       return Collections.<int[]> emptyList().iterator();
1596     }
1597     return hiddenColumns.iterator();
1598   }
1599
1600 }