Merge develop to Release_2_8_3_Branch
[jalview.git] / src / jalview / analysis / AlignmentUtils.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.analysis;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Map.Entry;
33 import java.util.Set;
34 import java.util.TreeMap;
35
36 import jalview.datamodel.AlignedCodon;
37 import jalview.datamodel.AlignedCodonFrame;
38 import jalview.datamodel.AlignmentAnnotation;
39 import jalview.datamodel.AlignmentI;
40 import jalview.datamodel.Mapping;
41 import jalview.datamodel.SearchResults;
42 import jalview.datamodel.Sequence;
43 import jalview.datamodel.SequenceGroup;
44 import jalview.datamodel.SequenceI;
45 import jalview.schemes.ResidueProperties;
46 import jalview.util.MapList;
47
48 /**
49  * grab bag of useful alignment manipulation operations Expect these to be
50  * refactored elsewhere at some point.
51  * 
52  * @author jimp
53  * 
54  */
55 public class AlignmentUtils
56 {
57
58   /**
59    * given an existing alignment, create a new alignment including all, or up to
60    * flankSize additional symbols from each sequence's dataset sequence
61    * 
62    * @param core
63    * @param flankSize
64    * @return AlignmentI
65    */
66   public static AlignmentI expandContext(AlignmentI core, int flankSize)
67   {
68     List<SequenceI> sq = new ArrayList<SequenceI>();
69     int maxoffset = 0;
70     for (SequenceI s : core.getSequences())
71     {
72       SequenceI newSeq = s.deriveSequence();
73       if (newSeq.getStart() > maxoffset
74               && newSeq.getDatasetSequence().getStart() < s.getStart())
75       {
76         maxoffset = newSeq.getStart();
77       }
78       sq.add(newSeq);
79     }
80     if (flankSize > -1)
81     {
82       maxoffset = flankSize;
83     }
84     // now add offset to create a new expanded alignment
85     for (SequenceI s : sq)
86     {
87       SequenceI ds = s;
88       while (ds.getDatasetSequence() != null)
89       {
90         ds = ds.getDatasetSequence();
91       }
92       int s_end = s.findPosition(s.getStart() + s.getLength());
93       // find available flanking residues for sequence
94       int ustream_ds = s.getStart() - ds.getStart(), dstream_ds = ds
95               .getEnd() - s_end;
96
97       // build new flanked sequence
98
99       // compute gap padding to start of flanking sequence
100       int offset = maxoffset - ustream_ds;
101
102       // padding is gapChar x ( maxoffset - min(ustream_ds, flank)
103       if (flankSize >= 0)
104       {
105         if (flankSize < ustream_ds)
106         {
107           // take up to flankSize residues
108           offset = maxoffset - flankSize;
109           ustream_ds = flankSize;
110         }
111         if (flankSize < dstream_ds)
112         {
113           dstream_ds = flankSize;
114         }
115       }
116       char[] upstream = new String(ds.getSequence(s.getStart() - 1
117               - ustream_ds, s.getStart() - 1)).toLowerCase().toCharArray();
118       char[] downstream = new String(ds.getSequence(s_end - 1, s_end + 1
119               + dstream_ds)).toLowerCase().toCharArray();
120       char[] coreseq = s.getSequence();
121       char[] nseq = new char[offset + upstream.length + downstream.length
122               + coreseq.length];
123       char c = core.getGapCharacter();
124       // TODO could lowercase the flanking regions
125       int p = 0;
126       for (; p < offset; p++)
127       {
128         nseq[p] = c;
129       }
130       // s.setSequence(new String(upstream).toLowerCase()+new String(coreseq) +
131       // new String(downstream).toLowerCase());
132       System.arraycopy(upstream, 0, nseq, p, upstream.length);
133       System.arraycopy(coreseq, 0, nseq, p + upstream.length,
134               coreseq.length);
135       System.arraycopy(downstream, 0, nseq, p + coreseq.length
136               + upstream.length, downstream.length);
137       s.setSequence(new String(nseq));
138       s.setStart(s.getStart() - ustream_ds);
139       s.setEnd(s_end + downstream.length);
140     }
141     AlignmentI newAl = new jalview.datamodel.Alignment(
142             sq.toArray(new SequenceI[0]));
143     for (SequenceI s : sq)
144     {
145       if (s.getAnnotation() != null)
146       {
147         for (AlignmentAnnotation aa : s.getAnnotation())
148         {
149           newAl.addAnnotation(aa);
150         }
151       }
152     }
153     newAl.setDataset(core.getDataset());
154     return newAl;
155   }
156
157   /**
158    * Returns the index (zero-based position) of a sequence in an alignment, or
159    * -1 if not found.
160    * 
161    * @param al
162    * @param seq
163    * @return
164    */
165   public static int getSequenceIndex(AlignmentI al, SequenceI seq)
166   {
167     int result = -1;
168     int pos = 0;
169     for (SequenceI alSeq : al.getSequences())
170     {
171       if (alSeq == seq)
172       {
173         result = pos;
174         break;
175       }
176       pos++;
177     }
178     return result;
179   }
180
181   /**
182    * Returns a map of lists of sequences in the alignment, keyed by sequence
183    * name. For use in mapping between different alignment views of the same
184    * sequences.
185    * 
186    * @see jalview.datamodel.AlignmentI#getSequencesByName()
187    */
188   public static Map<String, List<SequenceI>> getSequencesByName(
189           AlignmentI al)
190   {
191     Map<String, List<SequenceI>> theMap = new LinkedHashMap<String, List<SequenceI>>();
192     for (SequenceI seq : al.getSequences())
193     {
194       String name = seq.getName();
195       if (name != null)
196       {
197         List<SequenceI> seqs = theMap.get(name);
198         if (seqs == null)
199         {
200           seqs = new ArrayList<SequenceI>();
201           theMap.put(name, seqs);
202         }
203         seqs.add(seq);
204       }
205     }
206     return theMap;
207   }
208
209   /**
210    * Build mapping of protein to cDNA alignment. Mappings are made between
211    * sequences where the cDNA translates to the protein sequence. Any new
212    * mappings are added to the protein alignment. Returns true if any mappings
213    * either already exist or were added, else false.
214    * 
215    * @param proteinAlignment
216    * @param cdnaAlignment
217    * @return
218    */
219   public static boolean mapProteinToCdna(
220           final AlignmentI proteinAlignment,
221           final AlignmentI cdnaAlignment)
222   {
223     if (proteinAlignment == null || cdnaAlignment == null)
224     {
225       return false;
226     }
227
228     Set<SequenceI> mappedDna = new HashSet<SequenceI>();
229     Set<SequenceI> mappedProtein = new HashSet<SequenceI>();
230
231     /*
232      * First pass - map sequences where cross-references exist. This include
233      * 1-to-many mappings to support, for example, variant cDNA.
234      */
235     boolean mappingPerformed = mapProteinToCdna(proteinAlignment,
236             cdnaAlignment, mappedDna, mappedProtein, true);
237
238     /*
239      * Second pass - map sequences where no cross-references exist. This only
240      * does 1-to-1 mappings and assumes corresponding sequences are in the same
241      * order in the alignments.
242      */
243     mappingPerformed |= mapProteinToCdna(proteinAlignment, cdnaAlignment,
244             mappedDna, mappedProtein, false);
245     return mappingPerformed;
246   }
247
248   /**
249    * Make mappings between compatible sequences (where the cDNA translation
250    * matches the protein).
251    * 
252    * @param proteinAlignment
253    * @param cdnaAlignment
254    * @param mappedDna
255    *          a set of mapped DNA sequences (to add to)
256    * @param mappedProtein
257    *          a set of mapped Protein sequences (to add to)
258    * @param xrefsOnly
259    *          if true, only map sequences where xrefs exist
260    * @return
261    */
262   protected static boolean mapProteinToCdna(
263           final AlignmentI proteinAlignment,
264           final AlignmentI cdnaAlignment, Set<SequenceI> mappedDna,
265           Set<SequenceI> mappedProtein, boolean xrefsOnly)
266   {
267     boolean mappingPerformed = false;
268     List<SequenceI> thisSeqs = proteinAlignment.getSequences();
269     for (SequenceI aaSeq : thisSeqs)
270     {
271       boolean proteinMapped = false;
272       AlignedCodonFrame acf = new AlignedCodonFrame();
273
274       for (SequenceI cdnaSeq : cdnaAlignment.getSequences())
275       {
276         /*
277          * Always try to map if sequences have xref to each other; this supports
278          * variant cDNA or alternative splicing for a protein sequence.
279          * 
280          * If no xrefs, try to map progressively, assuming that alignments have
281          * mappable sequences in corresponding order. These are not
282          * many-to-many, as that would risk mixing species with similar cDNA
283          * sequences.
284          */
285         if (xrefsOnly && !CrossRef.haveCrossRef(aaSeq, cdnaSeq))
286         {
287           continue;
288         }
289
290         /*
291          * Don't map non-xrefd sequences more than once each. This heuristic
292          * allows us to pair up similar sequences in ordered alignments.
293          */
294         if (!xrefsOnly
295                 && (mappedProtein.contains(aaSeq) || mappedDna
296                         .contains(cdnaSeq)))
297         {
298           continue;
299         }
300         if (!mappingExists(proteinAlignment.getCodonFrames(),
301                 aaSeq.getDatasetSequence(), cdnaSeq.getDatasetSequence()))
302         {
303           MapList map = mapProteinToCdna(aaSeq, cdnaSeq);
304           if (map != null)
305           {
306             acf.addMap(cdnaSeq, aaSeq, map);
307             mappingPerformed = true;
308             proteinMapped = true;
309             mappedDna.add(cdnaSeq);
310             mappedProtein.add(aaSeq);
311           }
312         }
313       }
314       if (proteinMapped)
315       {
316         proteinAlignment.addCodonFrame(acf);
317       }
318     }
319     return mappingPerformed;
320   }
321
322   /**
323    * Answers true if the mappings include one between the given (dataset)
324    * sequences.
325    */
326   public static boolean mappingExists(Set<AlignedCodonFrame> set,
327           SequenceI aaSeq, SequenceI cdnaSeq)
328   {
329     if (set != null)
330     {
331       for (AlignedCodonFrame acf : set)
332       {
333         if (cdnaSeq == acf.getDnaForAaSeq(aaSeq))
334         {
335           return true;
336         }
337       }
338     }
339     return false;
340   }
341
342   /**
343    * Build a mapping (if possible) of a protein to a cDNA sequence. The cDNA
344    * must be three times the length of the protein, possibly after ignoring
345    * start and/or stop codons, and must translate to the protein. Returns null
346    * if no mapping is determined.
347    * 
348    * @param proteinSeqs
349    * @param cdnaSeq
350    * @return
351    */
352   public static MapList mapProteinToCdna(SequenceI proteinSeq,
353           SequenceI cdnaSeq)
354   {
355     /*
356      * Here we handle either dataset sequence set (desktop) or absent (applet).
357      * Use only the char[] form of the sequence to avoid creating possibly large
358      * String objects.
359      */
360     final SequenceI proteinDataset = proteinSeq.getDatasetSequence();
361     char[] aaSeqChars = proteinDataset != null ? proteinDataset
362             .getSequence() : proteinSeq.getSequence();
363     final SequenceI cdnaDataset = cdnaSeq.getDatasetSequence();
364     char[] cdnaSeqChars = cdnaDataset != null ? cdnaDataset.getSequence()
365             : cdnaSeq.getSequence();
366     if (aaSeqChars == null || cdnaSeqChars == null)
367     {
368       return null;
369     }
370
371     /*
372      * cdnaStart/End, proteinStartEnd are base 1 (for dataset sequence mapping)
373      */
374     final int mappedLength = 3 * aaSeqChars.length;
375     int cdnaLength = cdnaSeqChars.length;
376     int cdnaStart = 1;
377     int cdnaEnd = cdnaLength;
378     final int proteinStart = 1;
379     final int proteinEnd = aaSeqChars.length;
380
381     /*
382      * If lengths don't match, try ignoring stop codon.
383      */
384     if (cdnaLength != mappedLength && cdnaLength > 2)
385     {
386       String lastCodon = String.valueOf(cdnaSeqChars, cdnaLength - 3, 3)
387               .toUpperCase();
388       for (String stop : ResidueProperties.STOP)
389       {
390         if (lastCodon.equals(stop))
391         {
392           cdnaEnd -= 3;
393           cdnaLength -= 3;
394           break;
395         }
396       }
397     }
398
399     /*
400      * If lengths still don't match, try ignoring start codon.
401      */
402     if (cdnaLength != mappedLength
403             && cdnaLength > 2
404             && String.valueOf(cdnaSeqChars, 0, 3).toUpperCase()
405                     .equals(
406                     ResidueProperties.START))
407     {
408       cdnaStart += 3;
409       cdnaLength -= 3;
410     }
411
412     if (cdnaLength != mappedLength)
413     {
414       return null;
415     }
416     if (!translatesAs(cdnaSeqChars, cdnaStart - 1, aaSeqChars))
417     {
418       return null;
419     }
420     MapList map = new MapList(new int[]
421     { cdnaStart, cdnaEnd }, new int[]
422     { proteinStart, proteinEnd }, 3, 1);
423     return map;
424   }
425
426   /**
427    * Test whether the given cdna sequence, starting at the given offset,
428    * translates to the given amino acid sequence, using the standard translation
429    * table. Designed to fail fast i.e. as soon as a mismatch position is found.
430    * 
431    * @param cdnaSeqChars
432    * @param cdnaStart
433    * @param aaSeqChars
434    * @return
435    */
436   protected static boolean translatesAs(char[] cdnaSeqChars, int cdnaStart,
437           char[] aaSeqChars)
438   {
439     int aaResidue = 0;
440     for (int i = cdnaStart; i < cdnaSeqChars.length - 2
441             && aaResidue < aaSeqChars.length; i += 3, aaResidue++)
442     {
443       String codon = String.valueOf(cdnaSeqChars, i, 3);
444       final String translated = ResidueProperties.codonTranslate(
445               codon);
446       /*
447        * ? allow X in protein to match untranslatable in dna ?
448        */
449       final char aaRes = aaSeqChars[aaResidue];
450       if ((translated == null || "STOP".equals(translated)) && aaRes == 'X')
451       {
452         continue;
453       }
454       if (translated == null
455               || !(aaRes == translated.charAt(0)))
456       {
457         // debug
458         // System.out.println(("Mismatch at " + i + "/" + aaResidue + ": "
459         // + codon + "(" + translated + ") != " + aaRes));
460         return false;
461       }
462     }
463     // fail if we didn't match all of the aa sequence
464     return (aaResidue == aaSeqChars.length);
465   }
466
467   /**
468    * Align sequence 'seq' to match the alignment of a mapped sequence. Note this
469    * currently assumes that we are aligning cDNA to match protein.
470    * 
471    * @param seq
472    *          the sequence to be realigned
473    * @param al
474    *          the alignment whose sequence alignment is to be 'copied'
475    * @param gap
476    *          character string represent a gap in the realigned sequence
477    * @param preserveUnmappedGaps
478    * @param preserveMappedGaps
479    * @return true if the sequence was realigned, false if it could not be
480    */
481   public static boolean alignSequenceAs(SequenceI seq, AlignmentI al,
482           String gap, boolean preserveMappedGaps,
483           boolean preserveUnmappedGaps)
484   {
485     /*
486      * Get any mappings from the source alignment to the target (dataset) sequence.
487      */
488     // TODO there may be one AlignedCodonFrame per dataset sequence, or one with
489     // all mappings. Would it help to constrain this?
490     List<AlignedCodonFrame> mappings = al.getCodonFrame(seq);
491     if (mappings == null || mappings.isEmpty())
492     {
493       return false;
494     }
495   
496     /*
497      * Locate the aligned source sequence whose dataset sequence is mapped. We
498      * just take the first match here (as we can't align cDNA like more than one
499      * protein sequence).
500      */
501     SequenceI alignFrom = null;
502     AlignedCodonFrame mapping = null;
503     for (AlignedCodonFrame mp : mappings)
504     {
505       alignFrom = mp.findAlignedSequence(seq.getDatasetSequence(), al);
506       if (alignFrom != null)
507       {
508         mapping = mp;
509         break;
510       }
511     }
512   
513     if (alignFrom == null)
514     {
515       return false;
516     }
517     alignSequenceAs(seq, alignFrom, mapping, gap, al.getGapCharacter(),
518             preserveMappedGaps, preserveUnmappedGaps);
519     return true;
520   }
521
522   /**
523    * Align sequence 'alignTo' the same way as 'alignFrom', using the mapping to
524    * match residues and codons. Flags control whether existing gaps in unmapped
525    * (intron) and mapped (exon) regions are preserved or not. Gaps linking intro
526    * and exon are only retained if both flags are set.
527    * 
528    * @param alignTo
529    * @param alignFrom
530    * @param mapping
531    * @param myGap
532    * @param sourceGap
533    * @param preserveUnmappedGaps
534    * @param preserveMappedGaps
535    */
536   public static void alignSequenceAs(SequenceI alignTo,
537           SequenceI alignFrom,
538           AlignedCodonFrame mapping, String myGap, char sourceGap,
539           boolean preserveMappedGaps, boolean preserveUnmappedGaps)
540   {
541     // TODO generalise to work for Protein-Protein, dna-dna, dna-protein
542     final char[] thisSeq = alignTo.getSequence();
543     final char[] thatAligned = alignFrom.getSequence();
544     StringBuilder thisAligned = new StringBuilder(2 * thisSeq.length);
545   
546     // aligned and dataset sequence positions, all base zero
547     int thisSeqPos = 0;
548     int sourceDsPos = 0;
549
550     int basesWritten = 0;
551     char myGapChar = myGap.charAt(0);
552     int ratio = myGap.length();
553
554     /*
555      * Traverse the aligned protein sequence.
556      */
557     int sourceGapMappedLength = 0;
558     boolean inExon = false;
559     for (char sourceChar : thatAligned)
560     {
561       if (sourceChar == sourceGap)
562       {
563         sourceGapMappedLength += ratio;
564         continue;
565       }
566
567       /*
568        * Found a residue. Locate its mapped codon (start) position.
569        */
570       sourceDsPos++;
571       // Note mapping positions are base 1, our sequence positions base 0
572       int[] mappedPos = mapping.getMappedRegion(alignTo, alignFrom,
573               sourceDsPos);
574       if (mappedPos == null)
575       {
576         /*
577          * Abort realignment if unmapped protein. Or could ignore it??
578          */
579         System.err.println("Can't align: no codon mapping to residue "
580                 + sourceDsPos + "(" + sourceChar + ")");
581         return;
582       }
583
584       int mappedCodonStart = mappedPos[0]; // position (1...) of codon start
585       int mappedCodonEnd = mappedPos[mappedPos.length - 1]; // codon end pos
586       StringBuilder trailingCopiedGap = new StringBuilder();
587
588       /*
589        * Copy dna sequence up to and including this codon. Optionally, include
590        * gaps before the codon starts (in introns) and/or after the codon starts
591        * (in exons).
592        * 
593        * Note this only works for 'linear' splicing, not reverse or interleaved.
594        * But then 'align dna as protein' doesn't make much sense otherwise.
595        */
596       int intronLength = 0;
597       while (basesWritten < mappedCodonEnd && thisSeqPos < thisSeq.length)
598       {
599         final char c = thisSeq[thisSeqPos++];
600         if (c != myGapChar)
601         {
602           basesWritten++;
603
604           if (basesWritten < mappedCodonStart)
605           {
606             /*
607              * Found an unmapped (intron) base. First add in any preceding gaps
608              * (if wanted).
609              */
610             if (preserveUnmappedGaps && trailingCopiedGap.length() > 0)
611             {
612               thisAligned.append(trailingCopiedGap.toString());
613               intronLength += trailingCopiedGap.length();
614               trailingCopiedGap = new StringBuilder();
615             }
616             intronLength++;
617             inExon = false;
618           }
619           else
620           {
621             final boolean startOfCodon = basesWritten == mappedCodonStart;
622             int gapsToAdd = calculateGapsToInsert(preserveMappedGaps,
623                     preserveUnmappedGaps, sourceGapMappedLength, inExon,
624                     trailingCopiedGap.length(), intronLength, startOfCodon);
625             for (int i = 0; i < gapsToAdd; i++)
626             {
627               thisAligned.append(myGapChar);
628             }
629             sourceGapMappedLength = 0;
630             inExon = true;
631           }
632           thisAligned.append(c);
633           trailingCopiedGap = new StringBuilder();
634         }
635         else
636         {
637           if (inExon && preserveMappedGaps)
638           {
639             trailingCopiedGap.append(myGapChar);
640           }
641           else if (!inExon && preserveUnmappedGaps)
642           {
643             trailingCopiedGap.append(myGapChar);
644           }
645         }
646       }
647     }
648
649     /*
650      * At end of protein sequence. Copy any remaining dna sequence, optionally
651      * including (intron) gaps. We do not copy trailing gaps in protein.
652      */
653     while (thisSeqPos < thisSeq.length)
654     {
655       final char c = thisSeq[thisSeqPos++];
656       if (c != myGapChar || preserveUnmappedGaps)
657       {
658         thisAligned.append(c);
659       }
660     }
661
662     /*
663      * All done aligning, set the aligned sequence.
664      */
665     alignTo.setSequence(new String(thisAligned));
666   }
667
668   /**
669    * Helper method to work out how many gaps to insert when realigning.
670    * 
671    * @param preserveMappedGaps
672    * @param preserveUnmappedGaps
673    * @param sourceGapMappedLength
674    * @param inExon
675    * @param trailingCopiedGap
676    * @param intronLength
677    * @param startOfCodon
678    * @return
679    */
680   protected static int calculateGapsToInsert(boolean preserveMappedGaps,
681           boolean preserveUnmappedGaps, int sourceGapMappedLength,
682           boolean inExon, int trailingGapLength,
683           int intronLength, final boolean startOfCodon)
684   {
685     int gapsToAdd = 0;
686     if (startOfCodon)
687     {
688       /*
689        * Reached start of codon. Ignore trailing gaps in intron unless we are
690        * preserving gaps in both exon and intron. Ignore them anyway if the
691        * protein alignment introduces a gap at least as large as the intronic
692        * region.
693        */
694       if (inExon && !preserveMappedGaps)
695       {
696         trailingGapLength = 0;
697       }
698       if (!inExon && !(preserveMappedGaps && preserveUnmappedGaps))
699       {
700         trailingGapLength = 0;
701       }
702       if (inExon)
703       {
704         gapsToAdd = Math.max(sourceGapMappedLength, trailingGapLength);
705       }
706       else
707       {
708         if (intronLength + trailingGapLength <= sourceGapMappedLength)
709         {
710           gapsToAdd = sourceGapMappedLength - intronLength;
711         }
712         else
713         {
714           gapsToAdd = Math.min(intronLength + trailingGapLength
715                   - sourceGapMappedLength, trailingGapLength);
716         }
717       }
718     }
719     else
720     {
721       /*
722        * second or third base of codon; check for any gaps in dna
723        */
724       if (!preserveMappedGaps)
725       {
726         trailingGapLength = 0;
727       }
728       gapsToAdd = Math.max(sourceGapMappedLength, trailingGapLength);
729     }
730     return gapsToAdd;
731   }
732
733   /**
734    * Returns a list of sequences mapped from the given sequences and aligned
735    * (gapped) in the same way. For example, the cDNA for aligned protein, where
736    * a single gap in protein generates three gaps in cDNA.
737    * 
738    * @param sequences
739    * @param gapCharacter
740    * @param mappings
741    * @return
742    */
743   public static List<SequenceI> getAlignedTranslation(
744           List<SequenceI> sequences, char gapCharacter,
745           Set<AlignedCodonFrame> mappings)
746   {
747     List<SequenceI> alignedSeqs = new ArrayList<SequenceI>();
748
749     for (SequenceI seq : sequences)
750     {
751       List<SequenceI> mapped = getAlignedTranslation(seq, gapCharacter,
752               mappings);
753       alignedSeqs.addAll(mapped);
754     }
755     return alignedSeqs;
756   }
757
758   /**
759    * Returns sequences aligned 'like' the source sequence, as mapped by the
760    * given mappings. Normally we expect zero or one 'mapped' sequences, but this
761    * will support 1-to-many as well.
762    * 
763    * @param seq
764    * @param gapCharacter
765    * @param mappings
766    * @return
767    */
768   protected static List<SequenceI> getAlignedTranslation(SequenceI seq,
769           char gapCharacter, Set<AlignedCodonFrame> mappings)
770   {
771     List<SequenceI> result = new ArrayList<SequenceI>();
772     for (AlignedCodonFrame mapping : mappings)
773     {
774       if (mapping.involvesSequence(seq))
775       {
776         SequenceI mapped = getAlignedTranslation(seq, gapCharacter, mapping);
777         if (mapped != null)
778         {
779           result.add(mapped);
780         }
781       }
782     }
783     return result;
784   }
785
786   /**
787    * Returns the translation of 'seq' (as held in the mapping) with
788    * corresponding alignment (gaps).
789    * 
790    * @param seq
791    * @param gapCharacter
792    * @param mapping
793    * @return
794    */
795   protected static SequenceI getAlignedTranslation(SequenceI seq,
796           char gapCharacter, AlignedCodonFrame mapping)
797   {
798     String gap = String.valueOf(gapCharacter);
799     boolean toDna = false;
800     int fromRatio = 1;
801     SequenceI mapTo = mapping.getDnaForAaSeq(seq);
802     if (mapTo != null)
803     {
804       // mapping is from protein to nucleotide
805       toDna = true;
806       // should ideally get gap count ratio from mapping
807       gap = String.valueOf(new char[]
808       { gapCharacter, gapCharacter, gapCharacter });
809     }
810     else
811     {
812       // mapping is from nucleotide to protein
813       mapTo = mapping.getAaForDnaSeq(seq);
814       fromRatio = 3;
815     }
816     StringBuilder newseq = new StringBuilder(seq.getLength()
817             * (toDna ? 3 : 1));
818
819     int residueNo = 0; // in seq, base 1
820     int[] phrase = new int[fromRatio];
821     int phraseOffset = 0;
822     int gapWidth = 0;
823     boolean first = true;
824     final Sequence alignedSeq = new Sequence("", "");
825
826     for (char c : seq.getSequence())
827     {
828       if (c == gapCharacter)
829       {
830         gapWidth++;
831         if (gapWidth >= fromRatio)
832         {
833           newseq.append(gap);
834           gapWidth = 0;
835         }
836       }
837       else
838       {
839         phrase[phraseOffset++] = residueNo + 1;
840         if (phraseOffset == fromRatio)
841         {
842           /*
843            * Have read a whole codon (or protein residue), now translate: map
844            * source phrase to positions in target sequence add characters at
845            * these positions to newseq Note mapping positions are base 1, our
846            * sequence positions base 0.
847            */
848           SearchResults sr = new SearchResults();
849           for (int pos : phrase)
850           {
851             mapping.markMappedRegion(seq, pos, sr);
852           }
853           newseq.append(sr.toString());
854           if (first)
855           {
856             first = false;
857             // Hack: Copy sequence dataset, name and description from
858             // SearchResults.match[0].sequence
859             // TODO? carry over sequence names from original 'complement'
860             // alignment
861             SequenceI mappedTo = sr.getResultSequence(0);
862             alignedSeq.setName(mappedTo.getName());
863             alignedSeq.setDescription(mappedTo.getDescription());
864             alignedSeq.setDatasetSequence(mappedTo);
865           }
866           phraseOffset = 0;
867         }
868         residueNo++;
869       }
870     }
871     alignedSeq.setSequence(newseq.toString());
872     return alignedSeq;
873   }
874
875   /**
876    * Realigns the given protein to match the alignment of the dna, using codon
877    * mappings to translate aligned codon positions to protein residues.
878    * 
879    * @param protein
880    *          the alignment whose sequences are realigned by this method
881    * @param dna
882    *          the dna alignment whose alignment we are 'copying'
883    * @return the number of sequences that were realigned
884    */
885   public static int alignProteinAsDna(AlignmentI protein, AlignmentI dna)
886   {
887     Set<AlignedCodonFrame> mappings = protein.getCodonFrames();
888
889     /*
890      * Map will hold, for each aligned codon position e.g. [3, 5, 6], a map of
891      * {dnaSequence, {proteinSequence, codonProduct}} at that position. The
892      * comparator keeps the codon positions ordered.
893      */
894     Map<AlignedCodon, Map<SequenceI, String>> alignedCodons = new TreeMap<AlignedCodon, Map<SequenceI, String>>(
895             new CodonComparator());
896     for (SequenceI dnaSeq : dna.getSequences())
897     {
898       for (AlignedCodonFrame mapping : mappings)
899       {
900         Mapping seqMap = mapping.getMappingForSequence(dnaSeq);
901         SequenceI prot = mapping.findAlignedSequence(
902                 dnaSeq.getDatasetSequence(), protein);
903         if (prot != null)
904         {
905           addCodonPositions(dnaSeq, prot, protein.getGapCharacter(),
906                   seqMap, alignedCodons);
907         }
908       }
909     }
910     return alignProteinAs(protein, alignedCodons);
911   }
912
913   /**
914    * Update the aligned protein sequences to match the codon alignments given in
915    * the map.
916    * 
917    * @param protein
918    * @param alignedCodons
919    *          an ordered map of codon positions (columns), with sequence/peptide
920    *          values present in each column
921    * @return
922    */
923   protected static int alignProteinAs(AlignmentI protein,
924           Map<AlignedCodon, Map<SequenceI, String>> alignedCodons)
925   {
926     /*
927      * Prefill aligned sequences with gaps before inserting aligned protein
928      * residues.
929      */
930     int alignedWidth = alignedCodons.size();
931     char[] gaps = new char[alignedWidth];
932     Arrays.fill(gaps, protein.getGapCharacter());
933     String allGaps = String.valueOf(gaps);
934     for (SequenceI seq : protein.getSequences())
935     {
936       seq.setSequence(allGaps);
937     }
938
939     int column = 0;
940     for (AlignedCodon codon : alignedCodons.keySet())
941     {
942       final Map<SequenceI, String> columnResidues = alignedCodons.get(codon);
943       for (Entry<SequenceI, String> entry : columnResidues
944               .entrySet())
945       {
946         // place translated codon at its column position in sequence
947         entry.getKey().getSequence()[column] = entry.getValue().charAt(0);
948       }
949       column++;
950     }
951     return 0;
952   }
953
954   /**
955    * Populate the map of aligned codons by traversing the given sequence
956    * mapping, locating the aligned positions of mapped codons, and adding those
957    * positions and their translation products to the map.
958    * 
959    * @param dna
960    *          the aligned sequence we are mapping from
961    * @param protein
962    *          the sequence to be aligned to the codons
963    * @param gapChar
964    *          the gap character in the dna sequence
965    * @param seqMap
966    *          a mapping to a sequence translation
967    * @param alignedCodons
968    *          the map we are building up
969    */
970   static void addCodonPositions(SequenceI dna, SequenceI protein,
971           char gapChar,
972           Mapping seqMap,
973           Map<AlignedCodon, Map<SequenceI, String>> alignedCodons)
974   {
975     Iterator<AlignedCodon> codons = seqMap.getCodonIterator(dna, gapChar);
976     while (codons.hasNext())
977     {
978       AlignedCodon codon = codons.next();
979       Map<SequenceI, String> seqProduct = alignedCodons.get(codon);
980       if (seqProduct == null)
981       {
982         seqProduct = new HashMap<SequenceI, String>();
983         alignedCodons.put(codon, seqProduct);
984       }
985       seqProduct.put(protein, codon.product);
986     }
987   }
988
989   /**
990    * Returns true if a cDNA/Protein mapping either exists, or could be made,
991    * between at least one pair of sequences in the two alignments. Currently,
992    * the logic is:
993    * <ul>
994    * <li>One alignment must be nucleotide, and the other protein</li>
995    * <li>At least one pair of sequences must be already mapped, or mappable</li>
996    * <li>Mappable means the nucleotide translation matches the protein sequence</li>
997    * <li>The translation may ignore start and stop codons if present in the
998    * nucleotide</li>
999    * </ul>
1000    * 
1001    * @param al1
1002    * @param al2
1003    * @return
1004    */
1005   public static boolean isMappable(AlignmentI al1, AlignmentI al2)
1006   {
1007     /*
1008      * Require one nucleotide and one protein
1009      */
1010     if (al1.isNucleotide() == al2.isNucleotide())
1011     {
1012       return false;
1013     }
1014     AlignmentI dna = al1.isNucleotide() ? al1 : al2;
1015     AlignmentI protein = dna == al1 ? al2 : al1;
1016     Set<AlignedCodonFrame> mappings = protein.getCodonFrames();
1017     for (SequenceI dnaSeq : dna.getSequences())
1018     {
1019       for (SequenceI proteinSeq : protein.getSequences())
1020       {
1021         if (isMappable(dnaSeq, proteinSeq, mappings))
1022         {
1023           return true;
1024         }
1025       }
1026     }
1027     return false;
1028   }
1029
1030   /**
1031    * Returns true if the dna sequence is mapped, or could be mapped, to the
1032    * protein sequence.
1033    * 
1034    * @param dnaSeq
1035    * @param proteinSeq
1036    * @param mappings
1037    * @return
1038    */
1039   public static boolean isMappable(SequenceI dnaSeq, SequenceI proteinSeq,
1040           Set<AlignedCodonFrame> mappings)
1041   {
1042     SequenceI dnaDs = dnaSeq.getDatasetSequence() == null ? dnaSeq : dnaSeq.getDatasetSequence();
1043     SequenceI proteinDs = proteinSeq.getDatasetSequence() == null ? proteinSeq
1044             : proteinSeq.getDatasetSequence();
1045     
1046     /*
1047      * Already mapped?
1048      */
1049     for (AlignedCodonFrame mapping : mappings) {
1050       if ( proteinDs == mapping.getAaForDnaSeq(dnaDs)) {
1051         return true;
1052       }
1053     }
1054
1055     /*
1056      * Just try to make a mapping (it is not yet stored), test whether
1057      * successful.
1058      */
1059     return mapProteinToCdna(proteinDs, dnaDs) != null;
1060   }
1061
1062   /**
1063    * Finds any reference annotations associated with the sequences in
1064    * sequenceScope, that are not already added to the alignment, and adds them
1065    * to the 'candidates' map. Also populates a lookup table of annotation
1066    * labels, keyed by calcId, for use in constructing tooltips or the like.
1067    * 
1068    * @param sequenceScope
1069    *          the sequences to scan for reference annotations
1070    * @param labelForCalcId
1071    *          (optional) map to populate with label for calcId
1072    * @param candidates
1073    *          map to populate with annotations for sequence
1074    * @param al
1075    *          the alignment to check for presence of annotations
1076    */
1077   public static void findAddableReferenceAnnotations(
1078           List<SequenceI> sequenceScope, Map<String, String> labelForCalcId,
1079           final Map<SequenceI, List<AlignmentAnnotation>> candidates,
1080           AlignmentI al)
1081   {
1082     if (sequenceScope == null)
1083     {
1084       return;
1085     }
1086   
1087     /*
1088      * For each sequence in scope, make a list of any annotations on the
1089      * underlying dataset sequence which are not already on the alignment.
1090      * 
1091      * Add to a map of { alignmentSequence, <List of annotations to add> }
1092      */
1093     for (SequenceI seq : sequenceScope)
1094     {
1095       SequenceI dataset = seq.getDatasetSequence();
1096       if (dataset == null)
1097       {
1098         continue;
1099       }
1100       AlignmentAnnotation[] datasetAnnotations = dataset.getAnnotation();
1101       if (datasetAnnotations == null)
1102       {
1103         continue;
1104       }
1105       final List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1106       for (AlignmentAnnotation dsann : datasetAnnotations)
1107       {
1108         /*
1109          * Find matching annotations on the alignment. If none is found, then
1110          * add this annotation to the list of 'addable' annotations for this
1111          * sequence.
1112          */
1113         final Iterable<AlignmentAnnotation> matchedAlignmentAnnotations = al
1114                 .findAnnotations(seq, dsann.getCalcId(),
1115                         dsann.label);
1116         if (!matchedAlignmentAnnotations.iterator().hasNext())
1117         {
1118           result.add(dsann);
1119           if (labelForCalcId != null)
1120           {
1121             labelForCalcId.put(dsann.getCalcId(), dsann.label);
1122           }
1123         }
1124       }
1125       /*
1126        * Save any addable annotations for this sequence
1127        */
1128       if (!result.isEmpty())
1129       {
1130         candidates.put(seq, result);
1131       }
1132     }
1133   }
1134
1135   /**
1136    * Adds annotations to the top of the alignment annotations, in the same order
1137    * as their related sequences.
1138    * 
1139    * @param annotations
1140    *          the annotations to add
1141    * @param alignment
1142    *          the alignment to add them to
1143    * @param selectionGroup
1144    *          current selection group (or null if none)
1145    */
1146   public static void addReferenceAnnotations(
1147           Map<SequenceI, List<AlignmentAnnotation>> annotations,
1148           final AlignmentI alignment, final SequenceGroup selectionGroup)
1149   {
1150     for (SequenceI seq : annotations.keySet())
1151     {
1152       for (AlignmentAnnotation ann : annotations.get(seq))
1153       {
1154         AlignmentAnnotation copyAnn = new AlignmentAnnotation(ann);
1155         int startRes = 0;
1156         int endRes = ann.annotations.length;
1157         if (selectionGroup != null)
1158         {
1159           startRes = selectionGroup.getStartRes();
1160           endRes = selectionGroup.getEndRes();
1161         }
1162         copyAnn.restrict(startRes, endRes);
1163   
1164         /*
1165          * Add to the sequence (sets copyAnn.datasetSequence), unless the
1166          * original annotation is already on the sequence.
1167          */
1168         if (!seq.hasAnnotation(ann))
1169         {
1170           seq.addAlignmentAnnotation(copyAnn);
1171         }
1172         // adjust for gaps
1173         copyAnn.adjustForAlignment();
1174         // add to the alignment and set visible
1175         alignment.addAnnotation(copyAnn);
1176         copyAnn.visible = true;
1177       }
1178     }
1179   }
1180
1181   /**
1182    * Set visibility of alignment annotations of specified types (labels), for
1183    * specified sequences. This supports controls like
1184    * "Show all secondary structure", "Hide all Temp factor", etc.
1185    * 
1186    * @al the alignment to scan for annotations
1187    * @param types
1188    *          the types (labels) of annotations to be updated
1189    * @param forSequences
1190    *          if not null, only annotations linked to one of these sequences are
1191    *          in scope for update; if null, acts on all sequence annotations
1192    * @param anyType
1193    *          if this flag is true, 'types' is ignored (label not checked)
1194    * @param doShow
1195    *          if true, set visibility on, else set off
1196    */
1197   public static void showOrHideSequenceAnnotations(AlignmentI al,
1198           Collection<String> types, List<SequenceI> forSequences,
1199           boolean anyType, boolean doShow)
1200   {
1201     for (AlignmentAnnotation aa : al
1202             .getAlignmentAnnotation())
1203     {
1204       if (anyType || types.contains(aa.label))
1205       {
1206         if ((aa.sequenceRef != null)
1207                 && (forSequences == null || forSequences
1208                         .contains(aa.sequenceRef)))
1209         {
1210           aa.visible = doShow;
1211         }
1212       }
1213     }
1214   }
1215 }