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