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