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