JAL-1619 refactoring / tests to support 'align linked dna as protein'
[jalview.git] / src / jalview / datamodel / AlignedCodonFrame.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.util.MapList;
24
25 /**
26  * Stores mapping between the columns of a protein alignment and a DNA alignment
27  * and a list of individual codon to amino acid mappings between sequences.
28  */
29 public class AlignedCodonFrame
30 {
31
32   /*
33    * tied array of na Sequence objects.
34    */
35   private SequenceI[] dnaSeqs = null;
36
37   /*
38    * tied array of Mappings to protein sequence Objects and SequenceI[]
39    * aaSeqs=null; MapLists where each maps from the corresponding dnaSeqs
40    * element to corresponding aaSeqs element
41    */
42   private Mapping[] dnaToProt = null;
43
44   /**
45    * initialise codon frame with a nominal alignment width
46    * 
47    * @param aWidth
48    */
49   public AlignedCodonFrame()
50   {
51   }
52
53   /**
54    * add a mapping between the dataset sequences for the associated dna and
55    * protein sequence objects
56    * 
57    * @param dnaseq
58    * @param aaseq
59    * @param map
60    */
61   public void addMap(SequenceI dnaseq, SequenceI aaseq, MapList map)
62   {
63     int nlen = 1;
64     if (dnaSeqs != null)
65     {
66       nlen = dnaSeqs.length + 1;
67     }
68     SequenceI[] ndna = new SequenceI[nlen];
69     Mapping[] ndtp = new Mapping[nlen];
70     if (dnaSeqs != null)
71     {
72       System.arraycopy(dnaSeqs, 0, ndna, 0, dnaSeqs.length);
73       System.arraycopy(dnaToProt, 0, ndtp, 0, dnaSeqs.length);
74     }
75     dnaSeqs = ndna;
76     dnaToProt = ndtp;
77     nlen--;
78     dnaSeqs[nlen] = (dnaseq.getDatasetSequence() == null) ? dnaseq : dnaseq
79             .getDatasetSequence();
80     Mapping mp = new Mapping(map);
81     // JBPNote DEBUG! THIS !
82     // dnaseq.transferAnnotation(aaseq, mp);
83     // aaseq.transferAnnotation(dnaseq, new Mapping(map.getInverse()));
84     mp.to = (aaseq.getDatasetSequence() == null) ? aaseq : aaseq
85             .getDatasetSequence();
86     dnaToProt[nlen] = mp;
87   }
88
89   public SequenceI[] getdnaSeqs()
90   {
91     return dnaSeqs;
92   }
93
94   public SequenceI[] getAaSeqs()
95   {
96     if (dnaToProt == null)
97     {
98       return null;
99     }
100     SequenceI[] sqs = new SequenceI[dnaToProt.length];
101     for (int sz = 0; sz < dnaToProt.length; sz++)
102     {
103       sqs[sz] = dnaToProt[sz].to;
104     }
105     return sqs;
106   }
107
108   public MapList[] getdnaToProt()
109   {
110     if (dnaToProt == null)
111     {
112       return null;
113     }
114     MapList[] sqs = new MapList[dnaToProt.length];
115     for (int sz = 0; sz < dnaToProt.length; sz++)
116     {
117       sqs[sz] = dnaToProt[sz].map;
118     }
119     return sqs;
120   }
121
122   public Mapping[] getProtMappings()
123   {
124     return dnaToProt;
125   }
126
127   /**
128    * Return the corresponding aligned or dataset aa sequence for given dna
129    * sequence, null if not found.
130    * 
131    * @param sequenceRef
132    * @return
133    */
134   public SequenceI getAaForDnaSeq(SequenceI dnaSeqRef)
135   {
136     if (dnaSeqs == null)
137     {
138       return null;
139     }
140     SequenceI dnads = dnaSeqRef.getDatasetSequence();
141     for (int ds = 0; ds < dnaSeqs.length; ds++)
142     {
143       if (dnaSeqs[ds] == dnaSeqRef || dnaSeqs[ds] == dnads)
144       {
145         return dnaToProt[ds].to;
146       }
147     }
148     return null;
149   }
150
151   /**
152    * 
153    * @param sequenceRef
154    * @return null or corresponding aaSeq entry for dnaSeq entry
155    */
156   public SequenceI getDnaForAaSeq(SequenceI aaSeqRef)
157   {
158     if (dnaToProt == null)
159     {
160       return null;
161     }
162     SequenceI aads = aaSeqRef.getDatasetSequence();
163     for (int as = 0; as < dnaToProt.length; as++)
164     {
165       if (dnaToProt[as].to == aaSeqRef || dnaToProt[as].to == aads)
166       {
167         return dnaSeqs[as];
168       }
169     }
170     return null;
171   }
172
173   /**
174    * test to see if codon frame involves seq in any way
175    * 
176    * @param seq
177    *          a nucleotide or protein sequence
178    * @return true if a mapping exists to or from this sequence to any translated
179    *         sequence
180    */
181   public boolean involvesSequence(SequenceI seq)
182   {
183     return getAaForDnaSeq(seq) != null || getDnaForAaSeq(seq) != null;
184   }
185
186   /**
187    * Add search results for regions in other sequences that translate or are
188    * translated from a particular position in seq
189    * 
190    * @param seq
191    * @param index
192    *          position in seq
193    * @param results
194    *          where highlighted regions go
195    */
196   public void markMappedRegion(SequenceI seq, int index,
197           SearchResults results)
198   {
199     if (dnaToProt == null)
200     {
201       return;
202     }
203     int[] codon;
204     SequenceI ds = seq.getDatasetSequence();
205     for (int mi = 0; mi < dnaToProt.length; mi++)
206     {
207       if (dnaSeqs[mi] == seq || dnaSeqs[mi] == ds)
208       {
209         // DEBUG System.err.println("dna pos "+index);
210         codon = dnaToProt[mi].map.locateInTo(index, index);
211         if (codon != null)
212         {
213           for (int i = 0; i < codon.length; i += 2)
214           {
215             results.addResult(dnaToProt[mi].to, codon[i], codon[i + 1]);
216           }
217         }
218       }
219       else if (dnaToProt[mi].to == seq || dnaToProt[mi].to == ds)
220       {
221         // DEBUG System.err.println("aa pos "+index);
222         {
223           codon = dnaToProt[mi].map.locateInFrom(index, index);
224           if (codon != null)
225           {
226             for (int i = 0; i < codon.length; i += 2)
227             {
228               results.addResult(dnaSeqs[mi], codon[i], codon[i + 1]);
229             }
230           }
231         }
232       }
233     }
234   }
235
236   /**
237    * Returns the DNA codon positions (base 1) for the given position (base 1) in
238    * a mapped protein sequence, or null if no mapping is found.
239    * 
240    * Intended for use in aligning cDNA to match aligned protein. Only the first
241    * mapping found is returned, so not suitable for use if multiple protein
242    * sequences are mapped to the same cDNA (but aligning cDNA as protein is
243    * ill-defined for this case anyway).
244    * 
245    * @param seq
246    *          the DNA dataset sequence
247    * @param aaPos
248    *          residue position (base 1) in a protein sequence
249    * @return
250    */
251   public int[] getDnaPosition(SequenceI seq, int aaPos)
252   {
253     /*
254      * Adapted from markMappedRegion().
255      */
256     MapList ml = null;
257     for (int i = 0; i < dnaToProt.length; i++)
258     {
259       if (dnaSeqs[i] == seq)
260       {
261         ml = getdnaToProt()[i];
262         break;
263       }
264     }
265     return ml == null ? null : ml.locateInFrom(aaPos, aaPos);
266   }
267
268   /**
269    * Convenience method to return the first aligned sequence in the given
270    * alignment whose dataset has a mapping with the given dataset sequence.
271    * 
272    * @param seq
273    * 
274    * @param al
275    * @return
276    */
277   public SequenceI findAlignedSequence(SequenceI seq, AlignmentI al)
278   {
279     /*
280      * Search mapped protein ('to') sequences first.
281      */
282     if (this.dnaToProt != null)
283     {
284       for (int i = 0; i < dnaToProt.length; i++)
285       {
286         if (this.dnaSeqs[i] == seq)
287         {
288           for (SequenceI sourceAligned : al.getSequences())
289           {
290             if (this.dnaToProt[i].to == sourceAligned.getDatasetSequence())
291             {
292               return sourceAligned;
293             }
294           }
295         }
296       }
297     }
298
299     /*
300      * Then try mapped dna sequences.
301      */
302     if (this.dnaToProt != null)
303     {
304       for (int i = 0; i < dnaToProt.length; i++)
305       {
306         if (this.dnaToProt[i].to == seq)
307         {
308           for (SequenceI sourceAligned : al.getSequences())
309           {
310             if (this.dnaSeqs[i] == sourceAligned.getDatasetSequence())
311             {
312               return sourceAligned;
313             }
314           }
315         }
316       }
317     }
318
319     return null;
320   }
321
322   /**
323    * Returns the region in the 'mappedFrom' sequence's dataset that is mapped to
324    * position 'pos' (base 1) in the 'mappedTo' sequence's dataset. The region is
325    * a set of start/end position pairs.
326    * 
327    * @param mappedFrom
328    * @param mappedTo
329    * @param pos
330    * @return
331    */
332   public int[] getMappedRegion(SequenceI mappedFrom, SequenceI mappedTo,
333           int pos)
334   {
335     SequenceI targetDs = mappedFrom.getDatasetSequence() == null ? mappedFrom
336             : mappedFrom.getDatasetSequence();
337     SequenceI sourceDs = mappedTo.getDatasetSequence() == null ? mappedTo
338             : mappedTo.getDatasetSequence();
339     if (targetDs == null || sourceDs == null || dnaToProt == null)
340     {
341       return null;
342     }
343     for (int mi = 0; mi < dnaToProt.length; mi++)
344     {
345       if (dnaSeqs[mi] == targetDs && dnaToProt[mi].to == sourceDs)
346       {
347         int[] codon = dnaToProt[mi].map.locateInFrom(pos, pos);
348         if (codon != null) {
349           return codon;
350         }
351       }
352     }
353     return null;
354   }
355 }