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