JAL-3748 store an AlignmentView for the complement within an viewport’s AlignView...
[jalview.git] / src / jalview / datamodel / AlignmentView.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.MessageManager;
24 import jalview.util.ShiftList;
25
26 import java.io.PrintStream;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 /**
31  * Transient object compactly representing a 'view' of an alignment - with
32  * discontinuities marked. Extended in Jalview 2.7 to optionally record sequence
33  * groups and specific selected regions on the alignment.
34  */
35 public class AlignmentView
36 {
37   private SeqCigar[] sequences = null;
38
39   private int[] contigs = null;
40
41   private int width = 0;
42
43   private int firstCol = 0;
44
45   /**
46    * one or more ScGroup objects, which are referenced by each seqCigar's group
47    * membership
48    */
49   private List<ScGroup> scGroups = null;
50
51   private boolean isNa = false;
52
53   /**
54    * reference to the complementary CDS/Protein alignment for this alignment, if available
55    */
56   private AlignmentView complementView=null;
57   
58   /**
59    * setter for 
60    * @param complementView
61    */
62   public void setComplement(AlignmentView complementView)
63   {
64     this.complementView = complementView;
65     
66   }
67   /**
68    * 
69    * @return true if a complement is available
70    */
71   public boolean hasComplementView()
72   {
73     return complementView!=null;
74   }
75   /**
76    * 
77    * @return the complement view or null
78    */
79   public AlignmentView getComplementView()
80   {
81     return complementView;
82   }
83   
84   /**
85    * false if the view concerns peptides
86    * 
87    * @return
88    */
89   public boolean isNa()
90   {
91     return isNa;
92   }
93
94   /**
95    * Group defined over SeqCigars. Unlike AlignmentI associated groups, each
96    * SequenceGroup hold just the essential properties for the group, but no
97    * references to the sequences involved. SeqCigars hold references to the
98    * seuqenceGroup entities themselves.
99    */
100   private class ScGroup
101   {
102     public List<SeqCigar> seqs;
103
104     public SequenceGroup sg;
105
106     ScGroup()
107     {
108       seqs = new ArrayList<>();
109     }
110
111     /**
112      * @param seq
113      * @return true if seq was not a member before and was added to group
114      */
115     public boolean add(SeqCigar seq)
116     {
117       if (!seq.isMemberOf(this))
118       {
119         seqs.add(seq);
120         seq.setGroupMembership(this);
121         return true;
122       }
123       else
124       {
125         return false;
126       }
127     }
128
129     /**
130      * 
131      * @param seq
132      * @return true if seq was a member and was removed from group
133      */
134     public boolean remove(SeqCigar seq)
135     {
136       if (seq.removeGroupMembership(this))
137       {
138         seqs.remove(seq);
139         return true;
140       }
141       return false;
142     }
143
144     public int size()
145     {
146       return seqs.size();
147     }
148   }
149
150   /**
151    * vector of selected seqCigars. This vector is also referenced by each
152    * seqCigar contained in it.
153    */
154   private ScGroup selected;
155
156   /**
157    * Construct an alignmentView from a live jalview alignment view. Note -
158    * hidden rows will be excluded from alignmentView Note: JAL-1179
159    * 
160    * @param alignment
161    *          - alignment as referenced by an AlignViewport
162    * @param columnSelection
163    *          -
164    * @param selection
165    * @param hasHiddenColumns
166    *          - mark the hidden columns in columnSelection as hidden in the view
167    * @param selectedRegionOnly
168    *          - when set, only include the selected region in the view,
169    *          otherwise just mark the selected region on the constructed view.
170    * @param recordGroups
171    *          - when set, any groups on the given alignment will be marked on
172    *          the view
173    */
174   public AlignmentView(AlignmentI alignment, HiddenColumns hidden,
175           SequenceGroup selection, boolean hasHiddenColumns,
176           boolean selectedRegionOnly, boolean recordGroups)
177   {
178     // refactored from AlignViewport.getAlignmentView(selectedOnly);
179     this(new jalview.datamodel.CigarArray(alignment,
180             (hasHiddenColumns ? hidden : null),
181             (selectedRegionOnly ? selection : null)),
182             (selectedRegionOnly && selection != null)
183                     ? selection.getStartRes()
184                     : 0);
185     isNa = alignment.isNucleotide();
186     // walk down SeqCigar array and Alignment Array - optionally restricted by
187     // selected region.
188     // test group membership for each sequence in each group, store membership
189     // and record non-empty groups in group list.
190     // record / sub-select selected region on the alignment view
191     SequenceI[] selseqs;
192     if (selection != null && selection.getSize() > 0)
193     {
194       this.selected = new ScGroup();
195       selseqs = selection.getSequencesInOrder(alignment,
196               selectedRegionOnly);
197     }
198     else
199     {
200       selseqs = alignment.getSequencesArray();
201     }
202
203     List<List<SequenceI>> seqsets = new ArrayList<>();
204     // get the alignment's group list and make a copy
205     List<SequenceGroup> grps = new ArrayList<>();
206     List<SequenceGroup> gg = alignment.getGroups();
207     grps.addAll(gg);
208     ScGroup[] sgrps = null;
209     boolean addedgps[] = null;
210     if (grps != null)
211     {
212       if (selection != null && selectedRegionOnly)
213       {
214         // trim annotation to the region being stored.
215         // strip out any groups that do not actually intersect with the
216         // visible and selected region
217         int ssel = selection.getStartRes(), esel = selection.getEndRes();
218         List<SequenceGroup> isg = new ArrayList<>();
219         for (SequenceGroup sg : grps)
220         {
221           if (!(sg.getStartRes() > esel || sg.getEndRes() < ssel))
222           {
223             // adjust bounds of new group, if necessary.
224             if (sg.getStartRes() < ssel)
225             {
226               sg.setStartRes(ssel);
227             }
228             if (sg.getEndRes() > esel)
229             {
230               sg.setEndRes(esel);
231             }
232             sg.setStartRes(sg.getStartRes() - ssel + 1);
233             sg.setEndRes(sg.getEndRes() - ssel + 1);
234
235             isg.add(sg);
236           }
237         }
238         grps = isg;
239       }
240
241       sgrps = new ScGroup[grps.size()];
242       addedgps = new boolean[grps.size()];
243       for (int g = 0; g < sgrps.length; g++)
244       {
245         SequenceGroup sg = grps.get(g);
246         sgrps[g] = new ScGroup();
247         sgrps[g].sg = new SequenceGroup(sg);
248         addedgps[g] = false;
249         // can't set entry 0 in an empty list
250         // seqsets.set(g, sg.getSequences(null));
251         seqsets.add(sg.getSequences());
252       }
253       // seqsets now contains vectors (should be sets) for each group, so we can
254       // track when we've done with the group
255     }
256     int csi = 0;
257     for (int i = 0; i < selseqs.length; i++)
258     {
259       if (selseqs[i] != null)
260       {
261         if (selection != null && selection.getSize() > 0
262                 && !selectedRegionOnly)
263         {
264           selected.add(sequences[csi]);
265         }
266         if (seqsets != null)
267         {
268           for (int sg = 0; sg < sgrps.length; sg++)
269           {
270             if ((seqsets.get(sg)).contains(selseqs[i]))
271             {
272               sgrps[sg].sg.deleteSequence(selseqs[i], false);
273               sgrps[sg].add(sequences[csi]);
274               if (!addedgps[sg])
275               {
276                 if (scGroups == null)
277                 {
278                   scGroups = new ArrayList<>();
279                 }
280                 addedgps[sg] = true;
281                 scGroups.add(sgrps[sg]);
282               }
283             }
284           }
285         }
286         csi++;
287       }
288     }
289     // finally, delete the remaining sequences (if any) not selected
290     for (int sg = 0; sg < sgrps.length; sg++)
291     {
292       SequenceI[] sqs = sgrps[sg].sg.getSequencesAsArray(null);
293       for (int si = 0; si < sqs.length; si++)
294       {
295         sgrps[sg].sg.deleteSequence(sqs[si], false);
296       }
297       sgrps[sg] = null;
298     }
299   }
300
301   /**
302    * construct an alignmentView from a SeqCigarArray. Errors are thrown if the
303    * seqcigararray.isSeqCigarArray() flag is not set.
304    */
305   public AlignmentView(CigarArray seqcigararray)
306   {
307     if (!seqcigararray.isSeqCigarArray())
308     {
309       throw new Error(
310               "Implementation Error - can only make an alignment view from a CigarArray of sequences.");
311     }
312     // contigs = seqcigararray.applyDeletions();
313     contigs = seqcigararray.getDeletedRegions();
314     sequences = seqcigararray.getSeqCigarArray();
315     width = seqcigararray.getWidth(); // visible width
316   }
317
318   /**
319    * Create an alignmentView where the first column corresponds with the
320    * 'firstcol' column of some reference alignment
321    * 
322    * @param sdata
323    * @param firstcol
324    */
325   public AlignmentView(CigarArray sdata, int firstcol)
326   {
327     this(sdata);
328     firstCol = firstcol;
329   }
330
331   public void setSequences(SeqCigar[] sequences)
332   {
333     this.sequences = sequences;
334   }
335
336   public void setContigs(int[] contigs)
337   {
338     this.contigs = contigs;
339   }
340
341   public SeqCigar[] getSequences()
342   {
343     return sequences;
344   }
345
346   /**
347    * @see CigarArray.getDeletedRegions
348    * @return int[] { vis_start, sym_start, length }
349    */
350   public int[] getContigs()
351   {
352     return contigs;
353   }
354
355   /**
356    * get the full alignment and a columnselection object marking the hidden
357    * regions
358    * 
359    * @param gapCharacter
360    *          char
361    * @return Object[] { SequenceI[], ColumnSelection}
362    */
363   public Object[] getAlignmentAndHiddenColumns(char gapCharacter)
364   {
365     HiddenColumns hidden = new HiddenColumns();
366
367     return new Object[] { SeqCigar.createAlignmentSequences(sequences,
368             gapCharacter, hidden, contigs),
369         hidden };
370   }
371
372   /**
373    * return the visible alignment corresponding to this view. Sequences in this
374    * alignment are edited versions of the parent sequences - where hidden
375    * regions have been removed. NOTE: the sequence data in this alignment is not
376    * complete!
377    * 
378    * @param c
379    * @return
380    */
381   public AlignmentI getVisibleAlignment(char c)
382   {
383     SequenceI[] aln = getVisibleSeqs(c);
384
385     AlignmentI vcal = new Alignment(aln);
386     addPrunedGroupsInOrder(vcal, -1, -1, true);
387     return vcal;
388   }
389
390   /**
391    * add groups from view to the given alignment
392    * 
393    * @param vcal
394    * @param gstart
395    *          -1 or 0 to width-1
396    * @param gend
397    *          -1 or gstart to width-1
398    * @param viscontigs
399    *          - true if vcal is alignment of the visible regions of the view
400    *          (e.g. as returned from getVisibleAlignment)
401    */
402   private void addPrunedGroupsInOrder(AlignmentI vcal, int gstart, int gend,
403           boolean viscontigs)
404   {
405     boolean r = false;
406     if (gstart > -1 && gstart <= gend)
407     {
408       r = true;
409     }
410
411     SequenceI[] aln = vcal.getSequencesArray();
412     {
413       /**
414        * prune any groups to the visible coordinates of the alignment.
415        */
416       {
417         int nvg = (scGroups != null) ? scGroups.size() : 0;
418         if (nvg > 0)
419         {
420           SequenceGroup[] nsg = new SequenceGroup[nvg];
421           for (int g = 0; g < nvg; g++)
422           {
423             SequenceGroup sg = scGroups.get(g).sg;
424             if (r)
425             {
426               if (sg.getStartRes() > gend || sg.getEndRes() < gstart)
427               {
428                 // Skip this group
429                 nsg[g] = null;
430                 continue;
431               }
432             }
433
434             // clone group properties
435             nsg[g] = new SequenceGroup(sg);
436
437             // may need to shift/trim start and end ?
438             if (r && !viscontigs)
439             {
440               // Not fully tested code - routine not yet called with
441               // viscontigs==false
442               if (nsg[g].getStartRes() < gstart)
443               {
444                 nsg[g].setStartRes(0);
445               }
446               else
447               {
448                 nsg[g].setStartRes(nsg[g].getStartRes() - gstart);
449                 nsg[g].setEndRes(nsg[g].getEndRes() - gstart);
450               }
451               if (nsg[g].getEndRes() > (gend - gstart))
452               {
453                 nsg[g].setEndRes(gend - gstart);
454               }
455             }
456           }
457           if (viscontigs)
458           {
459             // prune groups to cover just the visible positions between
460             // gstart/gend.
461             if (contigs != null)
462             {
463               int p = 0;
464               ShiftList prune = new ShiftList();
465               if (r)
466               {
467                 // adjust for start of alignment within visible window.
468                 prune.addShift(gstart, -gstart); //
469               }
470               for (int h = 0; h < contigs.length; h += 3)
471               {
472                 {
473                   prune.addShift(p + contigs[h + 1],
474                           contigs[h + 2] - contigs[h + 1]);
475                 }
476                 p = contigs[h + 1] + contigs[h + 2];
477               }
478               for (int g = 0; g < nsg.length; g++)
479               {
480                 if (nsg[g] != null)
481                 {
482                   int s = nsg[g].getStartRes(), t = nsg[g].getEndRes();
483                   int w = 1 + t - s;
484                   if (r)
485                   {
486                     if (s < gstart)
487                     {
488                       s = gstart;
489                     }
490                     if (t > gend)
491                     {
492                       t = gend;
493                     }
494                   }
495                   s = prune.shift(s);
496                   t = prune.shift(t);
497                   nsg[g].setStartRes(s);
498                   nsg[g].setEndRes(t);
499                 }
500               }
501             }
502           }
503
504           for (int nsq = 0; nsq < aln.length; nsq++)
505           {
506             for (int g = 0; g < nvg; g++)
507             {
508               if (nsg[g] != null
509                       && sequences[nsq].isMemberOf(scGroups.get(g)))
510               {
511                 nsg[g].addSequence(aln[nsq], false);
512               }
513             }
514           }
515           for (int g = 0; g < nvg; g++)
516           {
517             if (nsg[g] != null && nsg[g].getSize() > 0)
518             {
519               vcal.addGroup(nsg[g]);
520             }
521             nsg[g] = null;
522           }
523         }
524       }
525     }
526   }
527
528   /**
529    * generate sequence array corresponding to the visible parts of the
530    * alignment.
531    * 
532    * @param c
533    *          gap character to use to recreate the alignment
534    * @return
535    */
536   private SequenceI[] getVisibleSeqs(char c)
537   {
538     SequenceI[] aln = new SequenceI[sequences.length];
539     for (int i = 0, j = sequences.length; i < j; i++)
540     {
541       aln[i] = sequences[i].getSeq(c);
542       // Remove hidden regions from sequence
543       aln[i].setSequence(getASequenceString(c, i));
544     }
545     return aln;
546   }
547
548   /**
549    * creates new alignment objects for all contiguous visible segments
550    * 
551    * @param c
552    * @param start
553    * @param end
554    * @param regionOfInterest
555    *          specify which sequences to include (or null to include all
556    *          sequences)
557    * @return AlignmentI[] - all alignments where each sequence is a subsequence
558    *         constructed from visible contig regions of view
559    */
560   public AlignmentI[] getVisibleContigAlignments(char c)
561   {
562     int nvc = 0;
563     int[] vcontigs = getVisibleContigs();
564     SequenceI[][] contigviews = getVisibleContigs(c);
565     AlignmentI[] vcals = new AlignmentI[contigviews.length];
566     for (nvc = 0; nvc < contigviews.length; nvc++)
567     {
568       vcals[nvc] = new Alignment(contigviews[nvc]);
569       if (scGroups != null && scGroups.size() > 0)
570       {
571         addPrunedGroupsInOrder(vcals[nvc], vcontigs[nvc * 2],
572                 vcontigs[nvc * 2 + 1], true);
573       }
574     }
575     return vcals;
576   }
577
578   /**
579    * build a string excluding hidden regions from a particular sequence in the
580    * view
581    * 
582    * @param c
583    * @param n
584    * @return
585    */
586   private String getASequenceString(char c, int n)
587   {
588     String sqn;
589     String fullseq = sequences[n].getSequenceString(c);
590     if (contigs != null)
591     {
592       sqn = "";
593       int p = 0;
594       for (int h = 0; h < contigs.length; h += 3)
595       {
596         sqn += fullseq.substring(p, contigs[h + 1]);
597         p = contigs[h + 1] + contigs[h + 2];
598       }
599       sqn += fullseq.substring(p);
600     }
601     else
602     {
603       sqn = fullseq;
604     }
605     return sqn;
606   }
607
608   /**
609    * get an array of visible sequence strings for a view on an alignment using
610    * the given gap character uses getASequenceString
611    * 
612    * @param c
613    *          char
614    * @return String[]
615    */
616   public String[] getSequenceStrings(char c)
617   {
618     String[] seqs = new String[sequences.length];
619     for (int n = 0; n < sequences.length; n++)
620     {
621       seqs[n] = getASequenceString(c, n);
622     }
623     return seqs;
624   }
625
626   /**
627    * 
628    * @return visible number of columns in alignment view
629    */
630   public int getWidth()
631   {
632     return width;
633   }
634
635   protected void setWidth(int width)
636   {
637     this.width = width;
638   }
639
640   /**
641    * get the contiguous subalignments in an alignment view.
642    * 
643    * @param gapCharacter
644    *          char
645    * @return SequenceI[][]
646    */
647   public SequenceI[][] getVisibleContigs(char gapCharacter)
648   {
649     SequenceI[][] smsa;
650     int njobs = 1;
651     if (sequences == null || width <= 0)
652     {
653       return null;
654     }
655     if (contigs != null && contigs.length > 0)
656     {
657       int start = 0;
658       njobs = 0;
659       int fwidth = width;
660       for (int contig = 0; contig < contigs.length; contig += 3)
661       {
662         if ((contigs[contig + 1] - start) > 0)
663         {
664           njobs++;
665         }
666         fwidth += contigs[contig + 2]; // end up with full region width
667         // (including hidden regions)
668         start = contigs[contig + 1] + contigs[contig + 2];
669       }
670       if (start < fwidth)
671       {
672         njobs++;
673       }
674       smsa = new SequenceI[njobs][];
675       start = 0;
676       int j = 0;
677       for (int contig = 0; contig < contigs.length; contig += 3)
678       {
679         if (contigs[contig + 1] - start > 0)
680         {
681           SequenceI mseq[] = new SequenceI[sequences.length];
682           for (int s = 0; s < mseq.length; s++)
683           {
684             mseq[s] = sequences[s].getSeq(gapCharacter)
685                     .getSubSequence(start, contigs[contig + 1]);
686           }
687           smsa[j] = mseq;
688           j++;
689         }
690         start = contigs[contig + 1] + contigs[contig + 2];
691       }
692       if (start < fwidth)
693       {
694         SequenceI mseq[] = new SequenceI[sequences.length];
695         for (int s = 0; s < mseq.length; s++)
696         {
697           mseq[s] = sequences[s].getSeq(gapCharacter).getSubSequence(start,
698                   fwidth + 1);
699         }
700         smsa[j] = mseq;
701         j++;
702       }
703     }
704     else
705     {
706       smsa = new SequenceI[1][];
707       smsa[0] = new SequenceI[sequences.length];
708       for (int s = 0; s < sequences.length; s++)
709       {
710         smsa[0][s] = sequences[s].getSeq(gapCharacter);
711       }
712     }
713     return smsa;
714   }
715
716   /**
717    * return full msa and hidden regions with visible blocks replaced with new
718    * sub alignments
719    * 
720    * @param nvismsa
721    *          SequenceI[][]
722    * @param orders
723    *          AlignmentOrder[] corresponding to each SequenceI[] block.
724    * @return Object[]
725    */
726   public Object[] getUpdatedView(SequenceI[][] nvismsa,
727           AlignmentOrder[] orders, char gapCharacter)
728   {
729     if (sequences == null || width <= 0)
730     {
731       throw new Error(MessageManager
732               .getString("error.empty_view_cannot_be_updated"));
733     }
734     if (nvismsa == null)
735     {
736       throw new Error(
737               "nvismsa==null. use getAlignmentAndColumnSelection() instead.");
738     }
739     if (contigs != null && contigs.length > 0)
740     {
741       SequenceI[] alignment = new SequenceI[sequences.length];
742       // ColumnSelection columnselection = new ColumnSelection();
743       HiddenColumns hidden = new HiddenColumns();
744       if (contigs != null && contigs.length > 0)
745       {
746         int start = 0;
747         int nwidth = 0;
748         int owidth = width;
749         int j = 0;
750         for (int contig = 0; contig < contigs.length; contig += 3)
751         {
752           owidth += contigs[contig + 2]; // recover final column width
753           if (contigs[contig + 1] - start > 0)
754           {
755             int swidth = 0; // subalignment width
756             if (nvismsa[j] != null)
757             {
758               SequenceI mseq[] = nvismsa[j];
759               AlignmentOrder order = (orders == null) ? null : orders[j];
760               j++;
761               if (mseq.length != sequences.length)
762               {
763                 throw new Error(MessageManager.formatMessage(
764                         "error.mismatch_between_number_of_sequences_in_block",
765                         new String[]
766                         { Integer.valueOf(j).toString(),
767                             Integer.valueOf(mseq.length).toString(),
768                             Integer.valueOf(sequences.length)
769                                     .toString() }));
770               }
771               swidth = mseq[0].getLength(); // JBPNote: could ensure padded
772               // here.
773               for (int s = 0; s < mseq.length; s++)
774               {
775                 if (alignment[s] == null)
776                 {
777                   alignment[s] = mseq[s];
778                 }
779                 else
780                 {
781                   alignment[s]
782                           .setSequence(alignment[s].getSequenceAsString()
783                                   + mseq[s].getSequenceAsString());
784                   if (mseq[s].getStart() <= mseq[s].getEnd())
785                   {
786                     alignment[s].setEnd(mseq[s].getEnd());
787                   }
788                   if (order != null)
789                   {
790                     order.updateSequence(mseq[s], alignment[s]);
791                   }
792                 }
793               }
794             }
795             else
796             {
797               // recover original alignment block or place gaps
798               if (true)
799               {
800                 // recover input data
801                 for (int s = 0; s < sequences.length; s++)
802                 {
803                   SequenceI oseq = sequences[s].getSeq(gapCharacter)
804                           .getSubSequence(start, contigs[contig + 1]);
805                   if (swidth < oseq.getLength())
806                   {
807                     swidth = oseq.getLength();
808                   }
809                   if (alignment[s] == null)
810                   {
811                     alignment[s] = oseq;
812                   }
813                   else
814                   {
815                     alignment[s]
816                             .setSequence(alignment[s].getSequenceAsString()
817                                     + oseq.getSequenceAsString());
818                     if (oseq.getEnd() >= oseq.getStart())
819                     {
820                       alignment[s].setEnd(oseq.getEnd());
821                     }
822                   }
823                 }
824
825               }
826               j++;
827             }
828             nwidth += swidth;
829           }
830           // advance to begining of visible region
831           start = contigs[contig + 1] + contigs[contig + 2];
832           // add hidden segment to right of next region
833           for (int s = 0; s < sequences.length; s++)
834           {
835             SequenceI hseq = sequences[s].getSeq(gapCharacter)
836                     .getSubSequence(contigs[contig + 1], start);
837             if (alignment[s] == null)
838             {
839               alignment[s] = hseq;
840             }
841             else
842             {
843               alignment[s].setSequence(alignment[s].getSequenceAsString()
844                       + hseq.getSequenceAsString());
845               if (hseq.getEnd() >= hseq.getStart())
846               {
847                 alignment[s].setEnd(hseq.getEnd());
848               }
849             }
850           }
851           // mark hidden segment as hidden in the new alignment
852           hidden.hideColumns(nwidth, nwidth + contigs[contig + 2] - 1);
853           nwidth += contigs[contig + 2];
854         }
855         // Do final segment - if it exists
856         if (j < nvismsa.length)
857         {
858           int swidth = 0;
859           if (nvismsa[j] != null)
860           {
861             SequenceI mseq[] = nvismsa[j];
862             AlignmentOrder order = (orders != null) ? orders[j] : null;
863             swidth = mseq[0].getLength();
864             for (int s = 0; s < mseq.length; s++)
865             {
866               if (alignment[s] == null)
867               {
868                 alignment[s] = mseq[s];
869               }
870               else
871               {
872                 alignment[s].setSequence(alignment[s].getSequenceAsString()
873                         + mseq[s].getSequenceAsString());
874                 if (mseq[s].getEnd() >= mseq[s].getStart())
875                 {
876                   alignment[s].setEnd(mseq[s].getEnd());
877                 }
878                 if (order != null)
879                 {
880                   order.updateSequence(mseq[s], alignment[s]);
881                 }
882               }
883             }
884           }
885           else
886           {
887             if (start < owidth)
888             {
889               // recover input data or place gaps
890               if (true)
891               {
892                 // recover input data
893                 for (int s = 0; s < sequences.length; s++)
894                 {
895                   SequenceI oseq = sequences[s].getSeq(gapCharacter)
896                           .getSubSequence(start, owidth + 1);
897                   if (swidth < oseq.getLength())
898                   {
899                     swidth = oseq.getLength();
900                   }
901                   if (alignment[s] == null)
902                   {
903                     alignment[s] = oseq;
904                   }
905                   else
906                   {
907                     alignment[s]
908                             .setSequence(alignment[s].getSequenceAsString()
909                                     + oseq.getSequenceAsString());
910                     if (oseq.getEnd() >= oseq.getStart())
911                     {
912                       alignment[s].setEnd(oseq.getEnd());
913                     }
914                   }
915                 }
916                 nwidth += swidth;
917               }
918               else
919               {
920                 // place gaps.
921                 throw new Error(MessageManager
922                         .getString("error.padding_not_yet_implemented"));
923               }
924             }
925           }
926         }
927       }
928       return new Object[] { alignment, hidden };
929     }
930     else
931     {
932       if (nvismsa.length != 1)
933       {
934         throw new Error(MessageManager.formatMessage(
935                 "error.mismatch_between_visible_blocks_to_update_and_number_of_contigs_in_view",
936                 new String[]
937                 { Integer.valueOf(nvismsa.length).toString() }));
938       }
939       if (nvismsa[0] != null)
940       {
941         return new Object[] { nvismsa[0], new HiddenColumns() };
942       }
943       else
944       {
945         return getAlignmentAndHiddenColumns(gapCharacter);
946       }
947     }
948   }
949
950   /**
951    * returns simple array of start end positions of visible range on alignment.
952    * vis_start and vis_end are inclusive - use
953    * SequenceI.getSubSequence(vis_start, vis_end+1) to recover visible sequence
954    * from underlying alignment.
955    * 
956    * @return int[] { start_i, end_i } for 1<i<n visible regions.
957    */
958   public int[] getVisibleContigs()
959   {
960     if (contigs != null && contigs.length > 0)
961     {
962       int start = 0;
963       int nvis = 0;
964       int fwidth = width;
965       for (int contig = 0; contig < contigs.length; contig += 3)
966       {
967         if ((contigs[contig + 1] - start) > 0)
968         {
969           nvis++;
970         }
971         fwidth += contigs[contig + 2]; // end up with full region width
972         // (including hidden regions)
973         start = contigs[contig + 1] + contigs[contig + 2];
974       }
975       if (start < fwidth)
976       {
977         nvis++;
978       }
979       int viscontigs[] = new int[nvis * 2];
980       nvis = 0;
981       start = 0;
982       for (int contig = 0; contig < contigs.length; contig += 3)
983       {
984         if ((contigs[contig + 1] - start) > 0)
985         {
986           viscontigs[nvis] = start;
987           viscontigs[nvis + 1] = contigs[contig + 1] - 1; // end is inclusive
988           nvis += 2;
989         }
990         start = contigs[contig + 1] + contigs[contig + 2];
991       }
992       if (start < fwidth)
993       {
994         viscontigs[nvis] = start;
995         viscontigs[nvis + 1] = fwidth - 1; // end is inclusive
996         nvis += 2;
997       }
998       return viscontigs;
999     }
1000     else
1001     {
1002       return new int[] { 0, width - 1 };
1003     }
1004   }
1005
1006   /**
1007    * 
1008    * @return position of first visible column of AlignmentView within its
1009    *         parent's alignment reference frame
1010    */
1011   public int getAlignmentOrigin()
1012   {
1013     return firstCol;
1014   }
1015
1016   /**
1017    * compute a deletion map for the current view according to the given
1018    * gap/match map
1019    * 
1020    * @param gapMap
1021    *          (as returned from SequenceI.gapMap())
1022    * @return int[] {intersection of visible regions with gapMap)
1023    */
1024   public int[] getVisibleContigMapFor(int[] gapMap)
1025   {
1026     int[] delMap = null;
1027     int[] viscontigs = getVisibleContigs();
1028     int spos = 0;
1029     int i = 0;
1030     if (viscontigs != null)
1031     {
1032       // viscontigs maps from a subset of the gapMap to the gapMap, so it will
1033       // always be equal to or shorter than gapMap
1034       delMap = new int[gapMap.length];
1035       for (int contig = 0; contig < viscontigs.length; contig += 2)
1036       {
1037
1038         while (spos < gapMap.length && gapMap[spos] < viscontigs[contig])
1039         {
1040           spos++;
1041         }
1042         while (spos < gapMap.length
1043                 && gapMap[spos] <= viscontigs[contig + 1])
1044         {
1045           delMap[i++] = spos++;
1046         }
1047       }
1048       int tmap[] = new int[i];
1049       System.arraycopy(delMap, 0, tmap, 0, i);
1050       delMap = tmap;
1051     }
1052     return delMap;
1053   }
1054
1055   /**
1056    * apply the getSeq(gc) method to each sequence cigar, and return the array of
1057    * edited sequences, optionally with hidden regions removed.
1058    * 
1059    * @param gc
1060    *          gap character to use for insertions
1061    * @param delete
1062    *          remove hidden regions from sequences. Note: currently implemented
1063    *          in a memory inefficient way - space needed is 2*result set for
1064    *          deletion
1065    * 
1066    * @return SequenceI[]
1067    */
1068   public SequenceI[] getEditedSequences(char gc, boolean delete)
1069   {
1070     SeqCigar[] msf = getSequences();
1071     SequenceI[] aln = new SequenceI[msf.length];
1072     for (int i = 0, j = msf.length; i < j; i++)
1073     {
1074       aln[i] = msf[i].getSeq(gc);
1075     }
1076     if (delete)
1077     {
1078       String[] sqs = getSequenceStrings(gc);
1079       for (int i = 0; i < sqs.length; i++)
1080       {
1081         aln[i].setSequence(sqs[i]);
1082         sqs[i] = null;
1083       }
1084     }
1085     return aln;
1086   }
1087
1088   public static void summariseAlignmentView(AlignmentView view,
1089           PrintStream os)
1090   {
1091     os.print("View has " + view.sequences.length + " of which ");
1092     if (view.selected == null)
1093     {
1094       os.print("None");
1095     }
1096     else
1097     {
1098       os.print(" " + view.selected.size());
1099     }
1100     os.println(" are selected.");
1101     os.print("View is " + view.getWidth() + " columns wide");
1102     int viswid = 0;
1103     int[] contigs = view.getContigs();
1104     if (contigs != null)
1105     {
1106       viswid = view.width;
1107       for (int i = 0; i < contigs.length; i += 3)
1108       {
1109         viswid += contigs[i + 2];
1110       }
1111       os.println("with " + viswid + " visible columns spread over "
1112               + contigs.length / 3 + " regions.");
1113     }
1114     else
1115     {
1116       viswid = view.width;
1117       os.println(".");
1118     }
1119     if (view.scGroups != null)
1120     {
1121       os.println("There are " + view.scGroups.size()
1122               + " groups defined on the view.");
1123       for (int g = 0; g < view.scGroups.size(); g++)
1124       {
1125         ScGroup sgr = view.scGroups.get(g);
1126         os.println("Group " + g + ": Name = " + sgr.sg.getName()
1127                 + " Contains " + sgr.seqs.size() + " Seqs.");
1128         os.println("This group runs from " + sgr.sg.getStartRes() + " to "
1129                 + sgr.sg.getEndRes());
1130         for (int s = 0; s < sgr.seqs.size(); s++)
1131         {
1132           // JBPnote this should be a unit test for ScGroup
1133           if (!sgr.seqs.get(s).isMemberOf(sgr))
1134           {
1135             os.println("** WARNING: sequence " + sgr.seqs.get(s).toString()
1136                     + " is not marked as member of group.");
1137           }
1138         }
1139       }
1140       AlignmentI visal = view.getVisibleAlignment('-');
1141       if (visal != null)
1142       {
1143         os.println("Vis. alignment is " + visal.getWidth()
1144                 + " wide and has " + visal.getHeight() + " seqs.");
1145         if (visal.getGroups() != null && visal.getGroups().size() > 0)
1146         {
1147
1148           int i = 1;
1149           for (SequenceGroup sg : visal.getGroups())
1150           {
1151             os.println("Group " + (i++) + " begins at column "
1152                     + sg.getStartRes() + " and ends at " + sg.getEndRes());
1153           }
1154         }
1155       }
1156     }
1157   }
1158
1159   public static void testSelectionViews(AlignmentI alignment,
1160           HiddenColumns hidden, SequenceGroup selection)
1161   {
1162     System.out.println("Testing standard view creation:\n");
1163     AlignmentView view = null;
1164     try
1165     {
1166       System.out.println(
1167               "View with no hidden columns, no limit to selection, no groups to be collected:");
1168       view = new AlignmentView(alignment, hidden, selection, false, false,
1169               false);
1170       summariseAlignmentView(view, System.out);
1171
1172     } catch (Exception e)
1173     {
1174       e.printStackTrace();
1175       System.err.println(
1176               "Failed to generate alignment with selection but no groups marked.");
1177     }
1178     try
1179     {
1180       System.out.println(
1181               "View with no hidden columns, no limit to selection, and all groups to be collected:");
1182       view = new AlignmentView(alignment, hidden, selection, false, false,
1183               true);
1184       summariseAlignmentView(view, System.out);
1185     } catch (Exception e)
1186     {
1187       e.printStackTrace();
1188       System.err.println(
1189               "Failed to generate alignment with selection marked but no groups marked.");
1190     }
1191     try
1192     {
1193       System.out.println(
1194               "View with no hidden columns, limited to selection and no groups to be collected:");
1195       view = new AlignmentView(alignment, hidden, selection, false, true,
1196               false);
1197       summariseAlignmentView(view, System.out);
1198     } catch (Exception e)
1199     {
1200       e.printStackTrace();
1201       System.err.println(
1202               "Failed to generate alignment with selection restricted but no groups marked.");
1203     }
1204     try
1205     {
1206       System.out.println(
1207               "View with no hidden columns, limited to selection, and all groups to be collected:");
1208       view = new AlignmentView(alignment, hidden, selection, false, true,
1209               true);
1210       summariseAlignmentView(view, System.out);
1211     } catch (Exception e)
1212     {
1213       e.printStackTrace();
1214       System.err.println(
1215               "Failed to generate alignment with selection restricted and groups marked.");
1216     }
1217     try
1218     {
1219       System.out.println(
1220               "View *with* hidden columns, no limit to selection, no groups to be collected:");
1221       view = new AlignmentView(alignment, hidden, selection, true, false,
1222               false);
1223       summariseAlignmentView(view, System.out);
1224     } catch (Exception e)
1225     {
1226       e.printStackTrace();
1227       System.err.println(
1228               "Failed to generate alignment with selection but no groups marked.");
1229     }
1230     try
1231     {
1232       System.out.println(
1233               "View *with* hidden columns, no limit to selection, and all groups to be collected:");
1234       view = new AlignmentView(alignment, hidden, selection, true, false,
1235               true);
1236       summariseAlignmentView(view, System.out);
1237     } catch (Exception e)
1238     {
1239       e.printStackTrace();
1240       System.err.println(
1241               "Failed to generate alignment with selection marked but no groups marked.");
1242     }
1243     try
1244     {
1245       System.out.println(
1246               "View *with* hidden columns, limited to selection and no groups to be collected:");
1247       view = new AlignmentView(alignment, hidden, selection, true, true,
1248               false);
1249       summariseAlignmentView(view, System.out);
1250     } catch (Exception e)
1251     {
1252       e.printStackTrace();
1253       System.err.println(
1254               "Failed to generate alignment with selection restricted but no groups marked.");
1255     }
1256     try
1257     {
1258       System.out.println(
1259               "View *with* hidden columns, limited to selection, and all groups to be collected:");
1260       view = new AlignmentView(alignment, hidden, selection, true, true,
1261               true);
1262       summariseAlignmentView(view, System.out);
1263     } catch (Exception e)
1264     {
1265       e.printStackTrace();
1266       System.err.println(
1267               "Failed to generate alignment with selection restricted and groups marked.");
1268     }
1269
1270   }
1271 }