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