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