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