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