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