JAL-2647 more iterators
[jalview.git] / src / jalview / datamodel / HiddenColumns.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.util.Comparison;
24
25 import java.util.ArrayList;
26 import java.util.BitSet;
27 import java.util.Collections;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Vector;
31 import java.util.concurrent.locks.ReentrantReadWriteLock;
32
33 public class HiddenColumns
34 {
35   private static final int HASH_MULTIPLIER = 31;
36
37   private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
38
39   /*
40    * list of hidden column [start, end] ranges; the list is maintained in
41    * ascending start column order
42    */
43   private ArrayList<int[]> hiddenColumns;
44
45   /**
46    * Constructor
47    */
48   public HiddenColumns()
49   {
50   }
51
52   /**
53    * Copy constructor
54    * 
55    * @param copy
56    */
57   public HiddenColumns(HiddenColumns copy)
58   {
59     try
60     {
61       LOCK.writeLock().lock();
62       if (copy != null)
63       {
64         if (copy.hiddenColumns != null)
65         {
66           hiddenColumns = copy.copyHiddenRegionsToArrayList(0);
67         }
68       }
69     } finally
70     {
71       LOCK.writeLock().unlock();
72     }
73   }
74
75   /**
76    * Copy constructor within bounds and with offset. Copies hidden column
77    * regions fully contained between start and end, and offsets positions by
78    * subtracting offset.
79    * 
80    * @param copy
81    *          HiddenColumns instance to copy from
82    * @param start
83    *          lower bound to copy from
84    * @param end
85    *          upper bound to copy to
86    * @param offset
87    *          offset to subtract from each region boundary position
88    * 
89    */
90   public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
91   {
92     try
93     {
94       LOCK.writeLock().lock();
95       if (copy != null)
96       {
97         hiddenColumns = new ArrayList<>();
98         Iterator<int[]> it = copy.getBoundedIterator(start, end, true);
99         while (it.hasNext())
100         {
101           int[] region = it.next();
102           // still need to check boundaries because iterator returns
103           // all overlapping regions and we need contained regions
104           if (region[0] >= start && region[1] <= end)
105           {
106             hiddenColumns.add(
107                     new int[]
108             { region[0] - offset, region[1] - offset });
109           }
110         }
111       }
112     } finally
113     {
114       LOCK.writeLock().unlock();
115     }
116   }
117
118   /**
119    * This method is used to return all the HiddenColumn regions and is intended
120    * to remain private. External callers which need a copy of the regions can
121    * call getHiddenColumnsCopyAsList.
122    * 
123    * @return empty list or List of hidden column intervals
124    */
125   private List<int[]> getHiddenRegions()
126   {
127     return hiddenColumns == null ? Collections.<int[]> emptyList()
128             : hiddenColumns;
129   }
130
131   /**
132    * Output regions data as a string. String is in the format:
133    * reg0[0]<between>reg0[1]<delimiter>reg1[0]<between>reg1[1] ... regn[1]
134    * 
135    * @param delimiter
136    *          string to delimit regions
137    * @param betweenstring
138    *          to put between start and end region values
139    * @return regions formatted according to delimiter and between strings
140    */
141   public String regionsToString(String delimiter, String between)
142   {
143     try
144     {
145       LOCK.readLock().lock();
146       StringBuilder regionBuilder = new StringBuilder();
147       if (hiddenColumns != null)
148       {
149         for (int[] range : hiddenColumns)
150         {
151           regionBuilder.append(delimiter).append(range[0]).append(between)
152                   .append(range[1]);
153         }
154
155         regionBuilder.deleteCharAt(0);
156       }
157       return regionBuilder.toString();
158     } finally
159     {
160       LOCK.readLock().unlock();
161     }
162   }
163
164   /**
165    * Find the number of hidden columns
166    * 
167    * @return number of hidden columns
168    */
169   public int getSize()
170   {
171     try
172     {
173       LOCK.readLock().lock();
174       int size = 0;
175       if (hasHiddenColumns())
176       {
177         for (int[] range : hiddenColumns)
178         {
179           size += range[1] - range[0] + 1;
180         }
181       }
182       return size;
183     } finally
184     {
185       LOCK.readLock().unlock();
186     }
187   }
188
189   /**
190    * Get the number of distinct hidden regions
191    * 
192    * @return number of regions
193    */
194   public int getNumberOfRegions()
195   {
196     try
197     {
198       LOCK.readLock().lock();
199       int num = 0;
200       if (hasHiddenColumns())
201       {
202         num = hiddenColumns.size();
203       }
204       return num;
205     } finally
206     {
207       LOCK.readLock().unlock();
208     }
209   }
210
211   @Override
212   public boolean equals(Object obj)
213   {
214     try
215     {
216       LOCK.readLock().lock();
217
218       if (!(obj instanceof HiddenColumns))
219       {
220         return false;
221       }
222       HiddenColumns that = (HiddenColumns) obj;
223
224       /*
225        * check hidden columns are either both null, or match
226        */
227       if (this.hiddenColumns == null)
228       {
229         return (that.hiddenColumns == null);
230       }
231       if (that.hiddenColumns == null
232               || that.hiddenColumns.size() != this.hiddenColumns.size())
233       {
234         return false;
235       }
236       int i = 0;
237       for (int[] thisRange : hiddenColumns)
238       {
239         int[] thatRange = that.hiddenColumns.get(i++);
240         if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
241         {
242           return false;
243         }
244       }
245       return true;
246     } finally
247     {
248       LOCK.readLock().unlock();
249     }
250   }
251
252   /**
253    * Return absolute column index for a visible column index
254    * 
255    * @param column
256    *          int column index in alignment view (count from zero)
257    * @return alignment column index for column
258    */
259   public int adjustForHiddenColumns(int column)
260   {
261     try
262     {
263       LOCK.readLock().lock();
264       int result = column;
265       if (hiddenColumns != null)
266       {
267         for (int i = 0; i < hiddenColumns.size(); i++)
268         {
269           int[] region = hiddenColumns.get(i);
270           if (result >= region[0])
271           {
272             result += region[1] - region[0] + 1;
273           }
274         }
275       }
276       return result;
277     } finally
278     {
279       LOCK.readLock().unlock();
280     }
281   }
282
283   /**
284    * Use this method to find out where a column will appear in the visible
285    * alignment when hidden columns exist. If the column is not visible, then the
286    * left-most visible column will always be returned.
287    * 
288    * @param hiddenColumn
289    *          the column index in the full alignment including hidden columns
290    * @return the position of the column in the visible alignment
291    */
292   public int findColumnPosition(int hiddenColumn)
293   {
294     try
295     {
296       LOCK.readLock().lock();
297       int result = hiddenColumn;
298       if (hiddenColumns != null)
299       {
300         int index = 0;
301         int[] region;
302         do
303         {
304           region = hiddenColumns.get(index++);
305           if (hiddenColumn > region[1])
306           {
307             result -= region[1] + 1 - region[0];
308           }
309         } while ((hiddenColumn > region[1])
310                 && (index < hiddenColumns.size()));
311
312         if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
313         {
314           // Here the hidden column is within a region, so
315           // we want to return the position of region[0]-1, adjusted for any
316           // earlier hidden columns.
317           // Calculate the difference between the actual hidden col position
318           // and region[0]-1, and then subtract from result to convert result
319           // from
320           // the adjusted hiddenColumn value to the adjusted region[0]-1 value
321
322           // However, if the region begins at 0 we cannot return region[0]-1
323           // just return 0
324           if (region[0] == 0)
325           {
326             return 0;
327           }
328           else
329           {
330             return result - (hiddenColumn - region[0] + 1);
331           }
332         }
333       }
334       return result; // return the shifted position after removing hidden
335                      // columns.
336     } finally
337     {
338       LOCK.readLock().unlock();
339     }
340   }
341
342   /**
343    * Find the visible column which is a given visible number of columns to the
344    * left of another visible column. i.e. for a startColumn x, the column which
345    * is distance 1 away will be column x-1.
346    * 
347    * @param visibleDistance
348    *          the number of visible columns to offset by
349    * @param startColumn
350    *          the column to start from
351    * @return the position of the column in the visible alignment
352    */
353   public int subtractVisibleColumns(int visibleDistance, int startColumn)
354   {
355     try
356     {
357
358       LOCK.readLock().lock();
359       int distance = visibleDistance;
360
361       // in case startColumn is in a hidden region, move it to the left
362       int start = adjustForHiddenColumns(findColumnPosition(startColumn));
363
364       // get index of hidden region to left of start
365       int index = getHiddenIndexLeft(start);
366       if (index == -1)
367       {
368         // no hidden regions to left of startColumn
369         return start - distance;
370       }
371
372       // walk backwards through the alignment subtracting the counts of visible
373       // columns from distance
374       int[] region;
375       int gap = 0;
376       int nextstart = start;
377
378       while ((index > -1) && (distance - gap > 0))
379       {
380         // subtract the gap to right of region from distance
381         distance -= gap;
382         start = nextstart;
383
384         // calculate the next gap
385         region = hiddenColumns.get(index);
386         gap = start - region[1];
387
388         // set start to just to left of current region
389         nextstart = region[0] - 1;
390         index--;
391       }
392
393       if (distance - gap > 0)
394       {
395         // fell out of loop because there are no more hidden regions
396         distance -= gap;
397         return nextstart - distance;
398       }
399       return start - distance;
400     } finally
401     {
402       LOCK.readLock().unlock();
403     }
404
405   }
406
407   /**
408    * Use this method to determine the set of hiddenRegion start positions
409    * between absolute position <start> and absolute position <end>
410    * 
411    * @param start
412    *          absolute residue to start from
413    * @param end
414    *          absolute residue to end at
415    * 
416    * @return list of column numbers in *visible* view where hidden regions start
417    */
418   public List<Integer> findHiddenRegionPositions(int start, int end)
419   {
420     try
421     {
422       LOCK.readLock().lock();
423       List<Integer> positions = null;
424
425       if (hiddenColumns != null)
426       {
427         positions = new ArrayList<>(hiddenColumns.size());
428
429         // navigate to start, keeping count of hidden columns
430         int i = 0;
431         int hiddenSoFar = 0;
432         while ((i < hiddenColumns.size())
433                 && (hiddenColumns.get(i)[0] < start))
434         {
435           int[] region = hiddenColumns.get(i);
436           hiddenSoFar += region[1] - region[0] + 1;
437           i++;
438         }
439
440         // iterate from start to end, adding start positions of each
441         // hidden region. Positions are visible columns count, not absolute
442         while (i < hiddenColumns.size()
443                 && (hiddenColumns.get(i)[0] < end))
444         {
445           int[] region = hiddenColumns.get(i);
446           positions.add(region[0] - hiddenSoFar);
447           hiddenSoFar += region[1] - region[0] + 1;
448           i++;
449         }
450       }
451       else
452       {
453         positions = new ArrayList<>();
454       }
455
456       return positions;
457     } finally
458     {
459       LOCK.readLock().unlock();
460     }
461   }
462
463   /**
464    * This method returns the rightmost limit of a region of an alignment with
465    * hidden columns. In otherwords, the next hidden column.
466    * 
467    * @param index
468    *          int
469    */
470   public int getHiddenBoundaryRight(int alPos)
471   {
472     try
473     {
474       LOCK.readLock().lock();
475       if (hiddenColumns != null)
476       {
477         int index = 0;
478         do
479         {
480           int[] region = hiddenColumns.get(index);
481           if (alPos < region[0])
482           {
483             return region[0];
484           }
485
486           index++;
487         } while (index < hiddenColumns.size());
488       }
489
490       return alPos;
491     } finally
492     {
493       LOCK.readLock().unlock();
494     }
495
496   }
497
498   /**
499    * This method returns the leftmost limit of a region of an alignment with
500    * hidden columns. In otherwords, the previous hidden column.
501    * 
502    * @param index
503    *          int
504    */
505   public int getHiddenBoundaryLeft(int alPos)
506   {
507     try
508     {
509       LOCK.readLock().lock();
510
511       if (hiddenColumns != null)
512       {
513         int index = hiddenColumns.size() - 1;
514         do
515         {
516           int[] region = hiddenColumns.get(index);
517           if (alPos > region[1])
518           {
519             return region[1];
520           }
521
522           index--;
523         } while (index > -1);
524       }
525
526       return alPos;
527     } finally
528     {
529       LOCK.readLock().unlock();
530     }
531   }
532
533   /**
534    * This method returns the index of the hidden region to the left of a column
535    * position. If the column is in a hidden region it returns the index of the
536    * region to the left. If there is no hidden region to the left it returns -1.
537    * 
538    * @param pos
539    *          int
540    */
541   private int getHiddenIndexLeft(int pos)
542   {
543     try
544     {
545
546       LOCK.readLock().lock();
547       if (hiddenColumns != null)
548       {
549         int index = hiddenColumns.size() - 1;
550         do
551         {
552           int[] region = hiddenColumns.get(index);
553           if (pos > region[1])
554           {
555             return index;
556           }
557
558           index--;
559         } while (index > -1);
560       }
561
562       return -1;
563     } finally
564     {
565       LOCK.readLock().unlock();
566     }
567
568   }
569
570   /**
571    * Adds the specified column range to the hidden columns
572    * 
573    * @param start
574    * @param end
575    */
576   public void hideColumns(int start, int end)
577   {
578     boolean wasAlreadyLocked = false;
579     try
580     {
581       // check if the write lock was already locked by this thread,
582       // as this method can be called internally in loops within HiddenColumns
583       if (!LOCK.isWriteLockedByCurrentThread())
584       {
585         LOCK.writeLock().lock();
586       }
587       else
588       {
589         wasAlreadyLocked = true;
590       }
591
592       if (hiddenColumns == null)
593       {
594         hiddenColumns = new ArrayList<>();
595       }
596
597       /*
598        * traverse existing hidden ranges and insert / amend / append as
599        * appropriate
600        */
601       for (int i = 0; i < hiddenColumns.size(); i++)
602       {
603         int[] region = hiddenColumns.get(i);
604
605         if (end < region[0] - 1)
606         {
607           /*
608            * insert discontiguous preceding range
609            */
610           hiddenColumns.add(i, new int[] { start, end });
611           return;
612         }
613
614         if (end <= region[1])
615         {
616           /*
617            * new range overlaps existing, or is contiguous preceding it - adjust
618            * start column
619            */
620           region[0] = Math.min(region[0], start);
621           return;
622         }
623
624         if (start <= region[1] + 1)
625         {
626           /*
627            * new range overlaps existing, or is contiguous following it - adjust
628            * start and end columns
629            */
630           region[0] = Math.min(region[0], start);
631           region[1] = Math.max(region[1], end);
632
633           /*
634            * also update or remove any subsequent ranges 
635            * that are overlapped
636            */
637           while (i < hiddenColumns.size() - 1)
638           {
639             int[] nextRegion = hiddenColumns.get(i + 1);
640             if (nextRegion[0] > end + 1)
641             {
642               /*
643                * gap to next hidden range - no more to update
644                */
645               break;
646             }
647             region[1] = Math.max(nextRegion[1], end);
648             hiddenColumns.remove(i + 1);
649           }
650           return;
651         }
652       }
653
654       /*
655        * remaining case is that the new range follows everything else
656        */
657       hiddenColumns.add(new int[] { start, end });
658     } finally
659     {
660       if (!wasAlreadyLocked)
661       {
662         LOCK.writeLock().unlock();
663       }
664     }
665   }
666
667   public boolean isVisible(int column)
668   {
669     try
670     {
671       LOCK.readLock().lock();
672
673       if (hiddenColumns != null)
674       {
675         for (int[] region : hiddenColumns)
676         {
677           if (column >= region[0] && column <= region[1])
678           {
679             return false;
680           }
681         }
682       }
683
684       return true;
685     } finally
686     {
687       LOCK.readLock().unlock();
688     }
689   }
690
691   private ArrayList<int[]> copyHiddenRegionsToArrayList(int startIndex)
692   {
693     int size = 0;
694     if (hiddenColumns != null)
695     {
696       size = hiddenColumns.size();
697     }
698     ArrayList<int[]> copy = new ArrayList<>(size);
699
700     for (int i = startIndex, j = size; i < j; i++)
701     {
702       int[] rh;
703       int[] cp;
704       rh = hiddenColumns.get(i);
705       if (rh != null)
706       {
707         cp = new int[rh.length];
708         System.arraycopy(rh, 0, cp, 0, rh.length);
709         copy.add(cp);
710       }
711     }
712
713     return copy;
714   }
715
716   /**
717    * Returns a copy of the vector of hidden regions, as an ArrayList. Before
718    * using this method please consider if you really need access to the hidden
719    * regions - a new (or existing!) method on HiddenColumns might be more
720    * appropriate.
721    * 
722    * @return hidden regions as an ArrayList of [start,end] pairs
723    */
724   public ArrayList<int[]> getHiddenColumnsCopy()
725   {
726     try
727     {
728       LOCK.readLock().lock();
729       return copyHiddenRegionsToArrayList(0);
730     } finally
731     {
732       LOCK.readLock().unlock();
733     }
734   }
735
736   /**
737    * return all visible segments between the given start and end boundaries
738    * 
739    * @param start
740    *          (first column inclusive from 0)
741    * @param end
742    *          (last column - not inclusive)
743    * @return List<int[]> {[i_start, i_end], ..} where intervals lie in
744    *         start<=i_start<=i_end<end
745    */
746   public List<int[]> getVisibleContigs(int start, int end)
747   {
748     try
749     {
750       LOCK.readLock().lock();
751       List<int[]> vcontigs = new ArrayList<>();
752       if (hiddenColumns != null && hiddenColumns.size() > 0)
753       {
754         int vstart = start;
755         int hideStart;
756         int hideEnd;
757
758         for (int[] region : hiddenColumns)
759         {
760           hideStart = region[0];
761           hideEnd = region[1];
762
763           // navigate to start
764           if (hideEnd < vstart)
765           {
766             continue;
767           }
768           if (hideStart > vstart)
769           {
770             int[] contig = new int[] { vstart, hideStart - 1 };
771             vcontigs.add(contig);
772           }
773           vstart = hideEnd + 1;
774
775           // exit if we're past the end
776           if (vstart >= end)
777           {
778             break;
779           }
780         }
781
782         if (vstart < end)
783         {
784           int[] contig = new int[] { vstart, end - 1 };
785           vcontigs.add(contig);
786         }
787       }
788       else
789       {
790         int[] contig = new int[] { start, end - 1 };
791         vcontigs.add(contig);
792       }
793       return vcontigs;
794     } finally
795     {
796       LOCK.readLock().unlock();
797     }
798   }
799
800   public String[] getVisibleSequenceStrings(int start, int end,
801           SequenceI[] seqs)
802   {
803     try
804     {
805       LOCK.readLock().lock();
806       int iSize = seqs.length;
807       String[] selections = new String[iSize];
808       if (hiddenColumns != null && hiddenColumns.size() > 0)
809       {
810         for (int i = 0; i < iSize; i++)
811         {
812           StringBuffer visibleSeq = new StringBuffer();
813
814           int blockStart = start;
815           int blockEnd = end;
816           int hideStart;
817           int hideEnd;
818
819           for (int[] region : hiddenColumns)
820           {
821             hideStart = region[0];
822             hideEnd = region[1];
823
824             if (hideStart < start)
825             {
826               continue;
827             }
828
829             blockStart = Math.min(blockStart, hideEnd + 1);
830             blockEnd = Math.min(blockEnd, hideStart);
831
832             if (blockStart > blockEnd)
833             {
834               break;
835             }
836
837             visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
838
839             blockStart = hideEnd + 1;
840             blockEnd = end;
841           }
842
843           if (end > blockStart)
844           {
845             visibleSeq.append(seqs[i].getSequence(blockStart, end));
846           }
847
848           selections[i] = visibleSeq.toString();
849         }
850       }
851       else
852       {
853         for (int i = 0; i < iSize; i++)
854         {
855           selections[i] = seqs[i].getSequenceAsString(start, end);
856         }
857       }
858
859       return selections;
860     } finally
861     {
862       LOCK.readLock().unlock();
863     }
864   }
865
866   /**
867    * Locate the first and last position visible for this sequence. if seq isn't
868    * visible then return the position of the left and right of the hidden
869    * boundary region, and the corresponding alignment column indices for the
870    * extent of the sequence
871    * 
872    * @param seq
873    * @return int[] { visible start, visible end, first seqpos, last seqpos,
874    *         alignment index for seq start, alignment index for seq end }
875    */
876   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
877   {
878     try
879     {
880       LOCK.readLock().lock();
881       int fpos = seq.getStart();
882       int lpos = seq.getEnd();
883       int start = 0;
884
885       if (hiddenColumns == null || hiddenColumns.size() == 0)
886       {
887         int ifpos = seq.findIndex(fpos) - 1;
888         int ilpos = seq.findIndex(lpos) - 1;
889         return new int[] { ifpos, ifpos, ilpos };
890       }
891
892       // Simply walk along the sequence whilst watching for hidden column
893       // boundaries
894       List<int[]> regions = getHiddenRegions();
895       int spos = fpos;
896       int rcount = 0;
897       int hideStart = seq.getLength();
898       int hideEnd = -1;
899       int visPrev = 0;
900       int visNext = 0;
901       int firstP = -1;
902       int lastP = -1;
903       boolean foundStart = false;
904       for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
905               && p < pLen; p++)
906       {
907         if (!Comparison.isGap(seq.getCharAt(p)))
908         {
909           // keep track of first/last column
910           // containing sequence data regardless of visibility
911           if (firstP == -1)
912           {
913             firstP = p;
914           }
915           lastP = p;
916           // update hidden region start/end
917           while (hideEnd < p && rcount < regions.size())
918           {
919             int[] region = regions.get(rcount++);
920             visPrev = visNext;
921             visNext += region[0] - visPrev;
922             hideStart = region[0];
923             hideEnd = region[1];
924           }
925           if (hideEnd < p)
926           {
927             hideStart = seq.getLength();
928           }
929           // update visible boundary for sequence
930           if (p < hideStart)
931           {
932             if (!foundStart)
933             {
934               fpos = spos;
935               start = p;
936               foundStart = true;
937             }
938             lpos = spos;
939           }
940           // look for next sequence position
941           spos++;
942         }
943       }
944       if (foundStart)
945       {
946         return new int[] { findColumnPosition(start), firstP, lastP };
947       }
948       // otherwise, sequence was completely hidden
949       return new int[] { visPrev, firstP, lastP };
950     } finally
951     {
952       LOCK.readLock().unlock();
953     }
954   }
955
956   /**
957    * delete any columns in alignmentAnnotation that are hidden (including
958    * sequence associated annotation).
959    * 
960    * @param alignmentAnnotation
961    */
962   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
963   {
964     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
965   }
966
967   /**
968    * delete any columns in alignmentAnnotation that are hidden (including
969    * sequence associated annotation).
970    * 
971    * @param start
972    *          remove any annotation to the right of this column
973    * @param end
974    *          remove any annotation to the left of this column
975    * @param alignmentAnnotation
976    *          the annotation to operate on
977    */
978   public void makeVisibleAnnotation(int start, int end,
979           AlignmentAnnotation alignmentAnnotation)
980   {
981     try
982     {
983       LOCK.readLock().lock();
984       if (alignmentAnnotation.annotations == null)
985       {
986         return;
987       }
988       if (start == end && end == -1)
989       {
990         start = 0;
991         end = alignmentAnnotation.annotations.length;
992       }
993       if (hiddenColumns != null && hiddenColumns.size() > 0)
994       {
995         // then mangle the alignmentAnnotation annotation array
996         Vector<Annotation[]> annels = new Vector<>();
997         Annotation[] els = null;
998         int blockStart = start;
999         int blockEnd = end;
1000         int hideStart;
1001         int hideEnd;
1002         int w = 0;
1003
1004         for (int[] region : hiddenColumns)
1005         {
1006           hideStart = region[0];
1007           hideEnd = region[1];
1008
1009           if (hideStart < start)
1010           {
1011             continue;
1012           }
1013
1014           blockStart = Math.min(blockStart, hideEnd + 1);
1015           blockEnd = Math.min(blockEnd, hideStart);
1016
1017           if (blockStart > blockEnd)
1018           {
1019             break;
1020           }
1021
1022           els = new Annotation[blockEnd - blockStart];
1023           annels.addElement(els);
1024           System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1025                   0, els.length);
1026           w += els.length;
1027           blockStart = hideEnd + 1;
1028           blockEnd = end;
1029         }
1030
1031         if (end > blockStart)
1032         {
1033           els = new Annotation[end - blockStart + 1];
1034           annels.addElement(els);
1035           if ((els.length
1036                   + blockStart) <= alignmentAnnotation.annotations.length)
1037           {
1038             // copy just the visible segment of the annotation row
1039             System.arraycopy(alignmentAnnotation.annotations, blockStart,
1040                     els, 0, els.length);
1041           }
1042           else
1043           {
1044             // copy to the end of the annotation row
1045             System.arraycopy(alignmentAnnotation.annotations, blockStart,
1046                     els, 0,
1047                     (alignmentAnnotation.annotations.length - blockStart));
1048           }
1049           w += els.length;
1050         }
1051         if (w == 0)
1052         {
1053           return;
1054         }
1055
1056         alignmentAnnotation.annotations = new Annotation[w];
1057         w = 0;
1058
1059         for (Annotation[] chnk : annels)
1060         {
1061           System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1062                   chnk.length);
1063           w += chnk.length;
1064         }
1065       }
1066       else
1067       {
1068         alignmentAnnotation.restrict(start, end);
1069       }
1070     } finally
1071     {
1072       LOCK.readLock().unlock();
1073     }
1074   }
1075
1076   /**
1077    * 
1078    * @return true if there are columns hidden
1079    */
1080   public boolean hasHiddenColumns()
1081   {
1082     try
1083     {
1084       LOCK.readLock().lock();
1085       return hiddenColumns != null && hiddenColumns.size() > 0;
1086     } finally
1087     {
1088       LOCK.readLock().unlock();
1089     }
1090   }
1091
1092   /**
1093    * 
1094    * @return true if there are more than one set of columns hidden
1095    */
1096   public boolean hasManyHiddenColumns()
1097   {
1098     try
1099     {
1100       LOCK.readLock().lock();
1101       return hiddenColumns != null && hiddenColumns.size() > 1;
1102     } finally
1103     {
1104       LOCK.readLock().unlock();
1105     }
1106   }
1107
1108   /**
1109    * mark the columns corresponding to gap characters as hidden in the column
1110    * selection
1111    * 
1112    * @param sr
1113    */
1114   public void hideInsertionsFor(SequenceI sr)
1115   {
1116     try
1117     {
1118       LOCK.writeLock().lock();
1119       List<int[]> inserts = sr.getInsertions();
1120       for (int[] r : inserts)
1121       {
1122         hideColumns(r[0], r[1]);
1123       }
1124     } finally
1125     {
1126       LOCK.writeLock().unlock();
1127     }
1128   }
1129
1130   /**
1131    * Unhides, and adds to the selection list, all hidden columns
1132    */
1133   public void revealAllHiddenColumns(ColumnSelection sel)
1134   {
1135     try
1136     {
1137       LOCK.writeLock().lock();
1138       if (hiddenColumns != null)
1139       {
1140         for (int i = 0; i < hiddenColumns.size(); i++)
1141         {
1142           int[] region = hiddenColumns.get(i);
1143           for (int j = region[0]; j < region[1] + 1; j++)
1144           {
1145             sel.addElement(j);
1146           }
1147         }
1148       }
1149
1150       hiddenColumns = null;
1151     } finally
1152     {
1153       LOCK.writeLock().unlock();
1154     }
1155   }
1156
1157   /**
1158    * Reveals, and marks as selected, the hidden column range with the given
1159    * start column
1160    * 
1161    * @param start
1162    */
1163   public void revealHiddenColumns(int start, ColumnSelection sel)
1164   {
1165     try
1166     {
1167       LOCK.writeLock().lock();
1168       for (int i = 0; i < hiddenColumns.size(); i++)
1169       {
1170         int[] region = hiddenColumns.get(i);
1171         if (start == region[0])
1172         {
1173           for (int j = region[0]; j < region[1] + 1; j++)
1174           {
1175             sel.addElement(j);
1176           }
1177
1178           hiddenColumns.remove(region);
1179           break;
1180         }
1181       }
1182       if (hiddenColumns.size() == 0)
1183       {
1184         hiddenColumns = null;
1185       }
1186     } finally
1187     {
1188       LOCK.writeLock().unlock();
1189     }
1190   }
1191
1192   /**
1193    * Add gaps into the sequences aligned to profileseq under the given
1194    * AlignmentView
1195    * 
1196    * @param profileseq
1197    * @param al
1198    *          - alignment to have gaps inserted into it
1199    * @param input
1200    *          - alignment view where sequence corresponding to profileseq is
1201    *          first entry
1202    * @return new HiddenColumns for new alignment view, with insertions into
1203    *         profileseq marked as hidden.
1204    */
1205   public static HiddenColumns propagateInsertions(SequenceI profileseq,
1206           AlignmentI al, AlignmentView input)
1207   {
1208     int profsqpos = 0;
1209
1210     char gc = al.getGapCharacter();
1211     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
1212     HiddenColumns nview = (HiddenColumns) alandhidden[1];
1213     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
1214     nview.propagateInsertions(profileseq, al, origseq);
1215     return nview;
1216   }
1217
1218   /**
1219    * 
1220    * @param profileseq
1221    *          - sequence in al which corresponds to origseq
1222    * @param al
1223    *          - alignment which is to have gaps inserted into it
1224    * @param origseq
1225    *          - sequence corresponding to profileseq which defines gap map for
1226    *          modifying al
1227    */
1228   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
1229           SequenceI origseq)
1230   {
1231     char gc = al.getGapCharacter();
1232
1233     // take the set of hidden columns, and the set of gaps in origseq,
1234     // and remove all the hidden gaps from hiddenColumns
1235
1236     // first get the gaps as a Bitset
1237     BitSet gaps = origseq.gapBitset();
1238
1239     // now calculate hidden ^ not(gap)
1240     BitSet hidden = new BitSet();
1241     markHiddenRegions(hidden);
1242     hidden.andNot(gaps);
1243     hiddenColumns = null;
1244     this.hideMarkedBits(hidden);
1245
1246     // for each sequence in the alignment, except the profile sequence,
1247     // insert gaps corresponding to each hidden region
1248     // but where each hidden column region is shifted backwards by the number of
1249     // preceding visible gaps
1250     // update hidden columns at the same time
1251     Iterator<int[]> regions = iterator();
1252     ArrayList<int[]> newhidden = new ArrayList<>();
1253
1254     int numGapsBefore = 0;
1255     int gapPosition = 0;
1256     while (regions.hasNext())
1257     {
1258       // get region coordinates accounting for gaps
1259       // we can rely on gaps not being *in* hidden regions because we already
1260       // removed those
1261       int[] region = regions.next();
1262       while (gapPosition < region[0])
1263       {
1264         gapPosition++;
1265         if (gaps.get(gapPosition))
1266         {
1267           numGapsBefore++;
1268         }
1269       }
1270
1271       int left = region[0] - numGapsBefore;
1272       int right = region[1] - numGapsBefore;
1273       newhidden.add(new int[] { left, right });
1274
1275       // make a string with number of gaps = length of hidden region
1276       StringBuffer sb = new StringBuffer();
1277       for (int s = 0; s < right - left + 1; s++)
1278       {
1279         sb.append(gc);
1280       }
1281       padGaps(sb, left, profileseq, al);
1282
1283     }
1284     hiddenColumns = newhidden;
1285   }
1286
1287   /**
1288    * Pad gaps in all sequences in alignment except profileseq
1289    * 
1290    * @param sb
1291    *          gap string to insert
1292    * @param left
1293    *          position to insert at
1294    * @param profileseq
1295    *          sequence not to pad
1296    * @param al
1297    *          alignment to pad sequences in
1298    */
1299   private void padGaps(StringBuffer sb, int pos, SequenceI profileseq,
1300           AlignmentI al)
1301   {
1302     // loop over the sequences and pad with gaps where required
1303     for (int s = 0, ns = al.getHeight(); s < ns; s++)
1304     {
1305       SequenceI sqobj = al.getSequenceAt(s);
1306       if (sqobj != profileseq)
1307       {
1308         String sq = al.getSequenceAt(s).getSequenceAsString();
1309         if (sq.length() <= pos)
1310         {
1311           // pad sequence
1312           int diff = pos - sq.length() - 1;
1313           if (diff > 0)
1314           {
1315             // pad gaps
1316             sq = sq + sb;
1317             while ((diff = pos - sq.length() - 1) > 0)
1318             {
1319               if (diff >= sb.length())
1320               {
1321                 sq += sb.toString();
1322               }
1323               else
1324               {
1325                 char[] buf = new char[diff];
1326                 sb.getChars(0, diff, buf, 0);
1327                 sq += buf.toString();
1328               }
1329             }
1330           }
1331           sq += sb.toString();
1332         }
1333         else
1334         {
1335           al.getSequenceAt(s).setSequence(
1336                   sq.substring(0, pos) + sb.toString() + sq.substring(pos));
1337         }
1338       }
1339     }
1340   }
1341
1342   /**
1343    * Returns a hashCode built from hidden column ranges
1344    */
1345   @Override
1346   public int hashCode()
1347   {
1348     try
1349     {
1350       LOCK.readLock().lock();
1351       int hashCode = 1;
1352       if (hiddenColumns != null)
1353       {
1354         for (int[] hidden : hiddenColumns)
1355         {
1356           hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
1357           hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
1358         }
1359       }
1360       return hashCode;
1361     } finally
1362     {
1363       LOCK.readLock().unlock();
1364     }
1365   }
1366
1367   /**
1368    * Hide columns corresponding to the marked bits
1369    * 
1370    * @param inserts
1371    *          - columns map to bits starting from zero
1372    */
1373   public void hideMarkedBits(BitSet inserts)
1374   {
1375     try
1376     {
1377       LOCK.writeLock().lock();
1378       for (int firstSet = inserts
1379               .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
1380                       .nextSetBit(lastSet))
1381       {
1382         lastSet = inserts.nextClearBit(firstSet);
1383         hideColumns(firstSet, lastSet - 1);
1384       }
1385     } finally
1386     {
1387       LOCK.writeLock().unlock();
1388     }
1389   }
1390
1391   /**
1392    * 
1393    * @param inserts
1394    *          BitSet where hidden columns will be marked
1395    */
1396   public void markHiddenRegions(BitSet inserts)
1397   {
1398     try
1399     {
1400       LOCK.readLock().lock();
1401       if (hiddenColumns == null)
1402       {
1403         return;
1404       }
1405       for (int[] range : hiddenColumns)
1406       {
1407         inserts.set(range[0], range[1] + 1);
1408       }
1409     } finally
1410     {
1411       LOCK.readLock().unlock();
1412     }
1413   }
1414
1415   /**
1416    * Calculate the visible start and end index of an alignment.
1417    * 
1418    * @param width
1419    *          full alignment width
1420    * @return integer array where: int[0] = startIndex, and int[1] = endIndex
1421    */
1422   public int[] getVisibleStartAndEndIndex(int width)
1423   {
1424     try
1425     {
1426       LOCK.readLock().lock();
1427       int[] alignmentStartEnd = new int[] { 0, width - 1 };
1428       int startPos = alignmentStartEnd[0];
1429       int endPos = alignmentStartEnd[1];
1430
1431       int[] lowestRange = new int[] { -1, -1 };
1432       int[] higestRange = new int[] { -1, -1 };
1433
1434       if (hiddenColumns == null)
1435       {
1436         return new int[] { startPos, endPos };
1437       }
1438
1439       for (int[] hiddenCol : hiddenColumns)
1440       {
1441         lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
1442         higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
1443       }
1444
1445       if (lowestRange[0] == -1 && lowestRange[1] == -1)
1446       {
1447         startPos = alignmentStartEnd[0];
1448       }
1449       else
1450       {
1451         startPos = lowestRange[1] + 1;
1452       }
1453
1454       if (higestRange[0] == -1 && higestRange[1] == -1)
1455       {
1456         endPos = alignmentStartEnd[1];
1457       }
1458       else
1459       {
1460         endPos = higestRange[0] - 1;
1461       }
1462       return new int[] { startPos, endPos };
1463     } finally
1464     {
1465       LOCK.readLock().unlock();
1466     }
1467
1468   }
1469
1470   /**
1471    * Finds the hidden region (if any) which starts or ends at res
1472    * 
1473    * @param res
1474    *          visible residue position, unadjusted for hidden columns
1475    * @return region as [start,end] or null if no matching region is found
1476    */
1477   public int[] getRegionWithEdgeAtRes(int res)
1478   {
1479     try
1480     {
1481       LOCK.readLock().lock();
1482       int adjres = adjustForHiddenColumns(res);
1483
1484       int[] reveal = null;
1485       if (hiddenColumns != null)
1486       {
1487         for (int[] region : hiddenColumns)
1488         {
1489           if (adjres + 1 == region[0] || adjres - 1 == region[1])
1490           {
1491             reveal = region;
1492             break;
1493           }
1494         }
1495       }
1496       return reveal;
1497     } finally
1498     {
1499       LOCK.readLock().unlock();
1500     }
1501   }
1502
1503   public Iterator<int[]> iterator()
1504   {
1505     if (hiddenColumns != null)
1506     {
1507       int last = hiddenColumns.get(hiddenColumns.size() - 1)[1];
1508       return new BoundedHiddenColsIterator(0, last, true);
1509     }
1510     else
1511     {
1512       return new BoundedHiddenColsIterator(0, 0, true);
1513     }
1514   }
1515
1516   public Iterator<int[]> getBoundedIterator(int start, int end,
1517           boolean useCopy)
1518   {
1519     return new BoundedHiddenColsIterator(start, end, useCopy);
1520   }
1521
1522   public Iterator<Integer> getBoundedStartIterator(int start, int end,
1523           boolean useCopy)
1524   {
1525     return new BoundedStartRegionIterator(start, end, useCopy);
1526   }
1527
1528   public Iterator<int[]> getBoundedVisRegionIterator(int start, int end)
1529   {
1530     return new BoundedVisRegionIterator(start, end, true);
1531   }
1532
1533   /**
1534    * An iterator which iterates over hidden column regions in a range.
1535    * 
1536    * @author kmourao
1537    *
1538    */
1539
1540
1541   class BoundedHiddenColsIterator implements Iterator<int[]>
1542   {
1543
1544     private int start; // start position to iterate from
1545
1546     private int end; // end position to iterate to
1547
1548     // current index in hiddenColumns
1549     private int currentPosition = 0;
1550
1551     // current column in hiddenColumns
1552     private int[] currentRegion;
1553
1554     // whether to make a local copy of hiddenColumns
1555     private final boolean useCopy;
1556
1557     // local copy or reference to hiddenColumns
1558     private List<int[]> localHidden;
1559
1560     /**
1561      * Construct an iterator over hiddenColums bounded at
1562      * [lowerBound,upperBound]
1563      * 
1564      * @param lowerBound
1565      *          lower bound to iterate from
1566      * @param upperBound
1567      *          upper bound to iterate to
1568      * @param useCopyCols
1569      *          whether to make a local copy of hiddenColumns for iteration (set
1570      *          to true if calling from outwith the HiddenColumns class)
1571      */
1572     BoundedHiddenColsIterator(int lowerBound, int upperBound,
1573             boolean useCopyCols)
1574     {
1575       start = lowerBound;
1576       end = upperBound;
1577       useCopy = useCopyCols;
1578
1579       try
1580       {
1581         if (useCopy)
1582         {
1583           // assume that if useCopy is false the calling code has locked
1584           // hiddenColumns
1585           LOCK.readLock().lock();
1586         }
1587         
1588         if (hiddenColumns != null)
1589         {
1590           localHidden = new ArrayList<>();
1591
1592           // iterate until a region overlaps with [start,end]
1593           int i = 0;
1594           while ((i < hiddenColumns.size())
1595                   && (hiddenColumns.get(i)[1] < start))
1596           {
1597             i++;
1598           }
1599
1600           // iterate from start to end, adding each hidden region. Positions are
1601           // absolute, and all regions which *overlap* [start,end] are added.
1602           while (i < hiddenColumns.size()
1603                   && (hiddenColumns.get(i)[0] <= end))
1604           {
1605             int[] rh;
1606             int[] cp;
1607             rh = hiddenColumns.get(i);
1608             if (rh != null)
1609             {
1610               cp = new int[rh.length];
1611               System.arraycopy(rh, 0, cp, 0, rh.length);
1612               localHidden.add(cp);
1613             }
1614             i++;
1615           }
1616         }
1617       }
1618       finally
1619       {
1620         if (useCopy)
1621         {
1622           LOCK.readLock().unlock();
1623         }
1624       }
1625     }
1626
1627     @Override
1628     public boolean hasNext()
1629     {
1630       return (localHidden != null)
1631               && (currentPosition < localHidden.size());
1632     }
1633
1634     @Override
1635     public int[] next()
1636     {
1637       currentRegion = localHidden.get(currentPosition);
1638       currentPosition++;
1639       return currentRegion;
1640     }
1641   }
1642
1643   class BoundedStartRegionIterator implements Iterator<Integer>
1644   {
1645
1646     private int start; // start position to iterate from
1647
1648     private int end; // end position to iterate to
1649
1650     // current index in hiddenColumns
1651     private int currentPosition = 0;
1652
1653     // local copy or reference to hiddenColumns
1654     private List<Integer> positions = null;
1655
1656     /**
1657      * Construct an iterator over hiddenColums bounded at
1658      * [lowerBound,upperBound]
1659      * 
1660      * @param lowerBound
1661      *          lower bound to iterate from
1662      * @param upperBound
1663      *          upper bound to iterate to
1664      * @param useCopyCols
1665      *          whether to make a local copy of hiddenColumns for iteration (set
1666      *          to true if calling from outwith the HiddenColumns class)
1667      */
1668     BoundedStartRegionIterator(int lowerBound, int upperBound,
1669             boolean useCopy)
1670     {
1671       start = lowerBound;
1672       end = upperBound;
1673       
1674       try
1675       {
1676         if (useCopy)
1677         {
1678           // assume that if useCopy is false the calling code has locked
1679           // hiddenColumns
1680           LOCK.readLock().lock();
1681         }
1682
1683         if (hiddenColumns != null)
1684         {
1685           positions = new ArrayList<>(hiddenColumns.size());
1686
1687           // navigate to start, keeping count of hidden columns
1688           int i = 0;
1689           int hiddenSoFar = 0;
1690           while ((i < hiddenColumns.size())
1691                   && (hiddenColumns.get(i)[0] < start + hiddenSoFar))
1692           {
1693             int[] region = hiddenColumns.get(i);
1694             hiddenSoFar += region[1] - region[0] + 1;
1695             i++;
1696           }
1697
1698           // iterate from start to end, adding start positions of each
1699           // hidden region. Positions are visible columns count, not absolute
1700           while (i < hiddenColumns.size()
1701                   && (hiddenColumns.get(i)[0] <= end + hiddenSoFar))
1702           {
1703             int[] region = hiddenColumns.get(i);
1704             positions.add(region[0] - hiddenSoFar);
1705             hiddenSoFar += region[1] - region[0] + 1;
1706             i++;
1707           }
1708         }
1709         else
1710         {
1711           positions = new ArrayList<>();
1712         }
1713       } finally
1714       {
1715         if (useCopy)
1716         {
1717           LOCK.readLock().unlock();
1718         }
1719       }
1720     }
1721
1722     @Override
1723     public boolean hasNext()
1724     {
1725       return (currentPosition < positions.size());
1726     }
1727
1728     @Override
1729     public Integer next()
1730     {
1731       int result = positions.get(currentPosition);
1732       currentPosition++;
1733       return result;
1734     }
1735   }
1736
1737   class BoundedVisRegionIterator implements Iterator<int[]>
1738   {
1739     private int start; // start position to iterate from
1740
1741     private int end; // end position to iterate to
1742
1743     // current region in visColumns
1744     private int[] currentRegion;
1745
1746     // current index in visColumns
1747     private int currentPosition = 0;
1748
1749     private List<int[]> vcontigs = null;
1750
1751     /**
1752      * Construct an iterator over visibleColumn regions bounded at
1753      * [lowerBound,upperBound]
1754      * 
1755      * @param lowerBound
1756      *          lower bound to iterate from
1757      * @param upperBound
1758      *          upper bound to iterate to
1759      * @param useCopyCols
1760      *          whether to make a local copy for iteration (set to true if
1761      *          calling from outwith the HiddenColumns class)
1762      */
1763     BoundedVisRegionIterator(int lowerBound, int upperBound,
1764             boolean useCopy)
1765     {
1766       try
1767       {
1768         start = lowerBound;
1769         end = upperBound;
1770
1771         if (useCopy)
1772         {
1773           // assume that if useCopy is false the calling code has locked
1774           // hiddenColumns
1775           LOCK.readLock().lock();
1776         }
1777
1778         int visStart = start;
1779
1780         if (hiddenColumns != null)
1781         {
1782           vcontigs = new ArrayList<>(hiddenColumns.size() + 1);
1783
1784           // navigate to start, keeping count of hidden columns
1785           int i = 0;
1786           int[] region = null;
1787           while ((i < hiddenColumns.size())
1788                   && (hiddenColumns.get(i)[0] <= start))
1789           {
1790             i++;
1791           }
1792           // if there was a hidden region before (i>=1), and it ended after
1793           // start
1794           // and before end, adjust visStart to be just after that region
1795           if (i > 0)
1796           {
1797             region = hiddenColumns.get(i - 1);
1798             if ((region[1] > start) && (region[1] < end))
1799             {
1800               visStart = region[1] + 1;
1801             }
1802             else if (region[1] >= end)
1803             {
1804               // previous hidden region covers whole range [start,end]
1805               // early exit - vcontigs is empty
1806               return;
1807             }
1808           }
1809
1810           // iterate from start to end, adding start positions of each
1811           // hidden region. Positions are visible columns count, not absolute
1812           while (i < hiddenColumns.size()
1813                   && (hiddenColumns.get(i)[0] < end))
1814           {
1815             region = hiddenColumns.get(i);
1816             int[] prevVisibleRegion = new int[] { visStart, region[0] - 1 };
1817             vcontigs.add(prevVisibleRegion);
1818             visStart = region[1] + 1;
1819             i++;
1820           }
1821           // add on a final visible region if needed
1822           if (visStart <= end)
1823           {
1824             int[] lastRegion = new int[] { visStart, end };
1825             vcontigs.add(lastRegion);
1826           }
1827         }
1828         else
1829         {
1830           vcontigs = new ArrayList<>();
1831           int[] lastRegion = new int[] { start, end };
1832           vcontigs.add(lastRegion);
1833         }
1834       } finally
1835       {
1836         if (useCopy)
1837         {
1838           LOCK.readLock().unlock();
1839         }
1840       }
1841     }
1842
1843     @Override
1844     public boolean hasNext()
1845     {
1846       return (currentPosition < vcontigs.size());
1847     }
1848
1849     @Override
1850     public int[] next()
1851     {
1852       currentRegion = vcontigs.get(currentPosition);
1853       currentPosition++;
1854       return currentRegion;
1855     }
1856   }
1857 }