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