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