b4489e2a4db01bd48152c7496b80e158b9a0ba9e
[jalview.git] / src / jalview / datamodel / Mapping.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.util.MapList;
24
25 import java.util.Iterator;
26 import java.util.NoSuchElementException;
27 import java.util.Vector;
28
29 public class Mapping
30 {
31   /**
32    * An iterator that serves the aligned codon positions (with their protein
33    * products).
34    * 
35    * @author gmcarstairs
36    *
37    */
38   public class AlignedCodonIterator implements Iterator<AlignedCodon>
39   {
40     /*
41      * The gap character used in the aligned sequence
42      */
43     private final char gap;
44
45     /*
46      * The characters of the aligned sequence e.g. "-cGT-ACgTG-"
47      */
48     private final char[] alignedSeq;
49
50     /*
51      * the sequence start residue
52      */
53     private int start;
54
55     /*
56      * Next position (base 0) in the aligned sequence
57      */
58     private int alignedColumn = 0;
59
60     /*
61      * Count of bases up to and including alignedColumn position
62      */
63     private int alignedBases = 0;
64
65     /*
66      * [start, end] from ranges (base 1)
67      */
68     private Iterator<int[]> fromRanges;
69
70     /*
71      * [start, end] to ranges (base 1)
72      */
73     private Iterator<int[]> toRanges;
74
75     /*
76      * The current [start, end] (base 1) from range
77      */
78     private int[] currentFromRange = null;
79
80     /*
81      * The current [start, end] (base 1) to range
82      */
83     private int[] currentToRange = null;
84
85     /*
86      * The next 'from' position (base 1) to process
87      */
88     private int fromPosition = 0;
89
90     /*
91      * The next 'to' position (base 1) to process
92      */
93     private int toPosition = 0;
94
95     /**
96      * Constructor
97      * 
98      * @param seq
99      *          the aligned sequence
100      * @param gapChar
101      */
102     public AlignedCodonIterator(SequenceI seq, char gapChar)
103     {
104       this.alignedSeq = seq.getSequence();
105       this.start = seq.getStart();
106       this.gap = gapChar;
107       fromRanges = map.getFromRanges().iterator();
108       toRanges = map.getToRanges().iterator();
109       if (fromRanges.hasNext())
110       {
111         currentFromRange = fromRanges.next();
112         fromPosition = currentFromRange[0];
113       }
114       if (toRanges.hasNext())
115       {
116         currentToRange = toRanges.next();
117         toPosition = currentToRange[0];
118       }
119     }
120
121     /**
122      * Returns true unless we have already traversed the whole mapping.
123      */
124     @Override
125     public boolean hasNext()
126     {
127       if (fromRanges.hasNext())
128       {
129         return true;
130       }
131       if (currentFromRange == null || fromPosition >= currentFromRange[1])
132       {
133         return false;
134       }
135       return true;
136     }
137
138     /**
139      * Returns the next codon's aligned positions, and translated value.
140      * 
141      * @throws NoSuchElementException
142      *           if hasNext() would have returned false
143      * @throws IncompleteCodonException
144      *           if not enough mapped bases are left to make up a codon
145      */
146     @Override
147     public AlignedCodon next() throws IncompleteCodonException
148     {
149       if (!hasNext())
150       {
151         throw new NoSuchElementException();
152       }
153
154       int[] codon = getNextCodon();
155       int[] alignedCodon = getAlignedCodon(codon);
156
157       String peptide = getPeptide();
158       int peptideCol = toPosition - 1 - Mapping.this.to.getStart();
159       return new AlignedCodon(alignedCodon[0], alignedCodon[1],
160               alignedCodon[2], peptide, peptideCol);
161     }
162
163     /**
164      * Retrieve the translation as the 'mapped to' position in the mapped to
165      * sequence.
166      * 
167      * @return
168      * @throws NoSuchElementException
169      *           if the 'toRange' is exhausted (nothing to map to)
170      */
171     private String getPeptide()
172     {
173       // TODO should ideally handle toRatio other than 1 as well...
174       // i.e. code like getNextCodon()
175       if (toPosition <= currentToRange[1])
176       {
177         SequenceI seq = Mapping.this.to;
178         char pep = seq.getSequence()[toPosition - seq.getStart()];
179         toPosition++;
180         return String.valueOf(pep);
181       }
182       if (!toRanges.hasNext())
183       {
184         throw new NoSuchElementException("Ran out of peptide at position "
185                 + toPosition);
186       }
187       currentToRange = toRanges.next();
188       toPosition = currentToRange[0];
189       return getPeptide();
190     }
191
192     /**
193      * Get the (base 1) dataset positions for the next codon in the mapping.
194      * 
195      * @throws IncompleteCodonException
196      *           if less than 3 remaining bases are mapped
197      */
198     private int[] getNextCodon()
199     {
200       int[] codon = new int[3];
201       int codonbase = 0;
202
203       while (codonbase < 3)
204       {
205         if (fromPosition <= currentFromRange[1])
206         {
207           /*
208            * Add next position from the current start-end range
209            */
210           codon[codonbase++] = fromPosition++;
211         }
212         else
213         {
214           /*
215            * Move to the next range - if there is one
216            */
217           if (!fromRanges.hasNext())
218           {
219             throw new IncompleteCodonException();
220           }
221           currentFromRange = fromRanges.next();
222           fromPosition = currentFromRange[0];
223         }
224       }
225       return codon;
226     }
227
228     /**
229      * Get the aligned column positions (base 0) for the given sequence
230      * positions (base 1), by counting ungapped characters in the aligned
231      * sequence.
232      * 
233      * @param codon
234      * @return
235      */
236     private int[] getAlignedCodon(int[] codon)
237     {
238       int[] aligned = new int[codon.length];
239       for (int i = 0; i < codon.length; i++)
240       {
241         aligned[i] = getAlignedColumn(codon[i]);
242       }
243       return aligned;
244     }
245
246     /**
247      * Get the aligned column position (base 0) for the given sequence position
248      * (base 1).
249      * 
250      * @param sequencePos
251      * @return
252      */
253     private int getAlignedColumn(int sequencePos)
254     {
255       /*
256        * allow for offset e.g. treat pos 8 as 2 if sequence starts at 7
257        */
258       int truePos = sequencePos - (start - 1);
259       while (alignedBases < truePos && alignedColumn < alignedSeq.length)
260       {
261         if (alignedSeq[alignedColumn++] != gap)
262         {
263           alignedBases++;
264         }
265       }
266       return alignedColumn - 1;
267     }
268
269     @Override
270     public void remove()
271     {
272       // ignore
273     }
274
275   }
276
277   /**
278    * Contains the start-end pairs mapping from the associated sequence to the
279    * sequence in the database coordinate system. It also takes care of step
280    * difference between coordinate systems.
281    */
282   MapList map = null;
283
284   /**
285    * The sequence that map maps the associated sequence to (if any).
286    */
287   SequenceI to = null;
288
289   public Mapping(MapList map)
290   {
291     super();
292     this.map = map;
293   }
294
295   public Mapping(SequenceI to, MapList map)
296   {
297     this(map);
298     this.to = to;
299   }
300
301   /**
302    * create a new mapping from
303    * 
304    * @param to
305    *          the sequence being mapped
306    * @param exon
307    *          int[] {start,end,start,end} series on associated sequence
308    * @param is
309    *          int[] {start,end,...} ranges on the reference frame being mapped
310    *          to
311    * @param i
312    *          step size on associated sequence
313    * @param j
314    *          step size on mapped frame
315    */
316   public Mapping(SequenceI to, int[] exon, int[] is, int i, int j)
317   {
318     this(to, new MapList(exon, is, i, j));
319   }
320
321   /**
322    * create a duplicate (and independent) mapping object with the same reference
323    * to any SequenceI being mapped to.
324    * 
325    * @param map2
326    */
327   public Mapping(Mapping map2)
328   {
329     if (map2 != this && map2 != null)
330     {
331       if (map2.map != null)
332       {
333         map = new MapList(map2.map);
334       }
335       to = map2.to;
336     }
337   }
338
339   /**
340    * @return the map
341    */
342   public MapList getMap()
343   {
344     return map;
345   }
346
347   /**
348    * @param map
349    *          the map to set
350    */
351   public void setMap(MapList map)
352   {
353     this.map = map;
354   }
355
356   /**
357    * Equals that compares both the to references and MapList mappings.
358    * 
359    * @param o
360    * @return
361    * @see MapList#equals
362    */
363   @Override
364   public boolean equals(Object o)
365   {
366     if (o == null || !(o instanceof Mapping))
367     {
368       return false;
369     }
370     Mapping other = (Mapping) o;
371     if (other == this)
372     {
373       return true;
374     }
375     if (other.to != to)
376     {
377       return false;
378     }
379     if ((map != null && other.map == null)
380             || (map == null && other.map != null))
381     {
382       return false;
383     }
384     if ((map == null && other.map == null) || map.equals(other.map))
385     {
386       return true;
387     }
388     return false;
389   }
390
391   /**
392    * Returns a hashCode made from the sequence and maplist
393    */
394   @Override
395   public int hashCode()
396   {
397     int hashCode = (this.to == null ? 1 : this.to.hashCode());
398     if (this.map != null)
399     {
400       hashCode = hashCode * 31 + this.map.hashCode();
401     }
402
403     return hashCode;
404   }
405
406   /**
407    * get the 'initial' position in the associated sequence for a position in the
408    * mapped reference frame
409    * 
410    * @param mpos
411    * @return
412    */
413   public int getPosition(int mpos)
414   {
415     if (map != null)
416     {
417       int[] mp = map.shiftTo(mpos);
418       if (mp != null)
419       {
420         return mp[0];
421       }
422     }
423     return mpos;
424   }
425
426   /**
427    * gets boundary in direction of mapping
428    * 
429    * @param position
430    *          in mapped reference frame
431    * @return int{start, end} positions in associated sequence (in direction of
432    *         mapped word)
433    */
434   public int[] getWord(int mpos)
435   {
436     if (map != null)
437     {
438       return map.getToWord(mpos);
439     }
440     return null;
441   }
442
443   /**
444    * width of mapped unit in associated sequence
445    * 
446    */
447   public int getWidth()
448   {
449     if (map != null)
450     {
451       return map.getFromRatio();
452     }
453     return 1;
454   }
455
456   /**
457    * width of unit in mapped reference frame
458    * 
459    * @return
460    */
461   public int getMappedWidth()
462   {
463     if (map != null)
464     {
465       return map.getToRatio();
466     }
467     return 1;
468   }
469
470   /**
471    * get mapped position in the associated reference frame for position pos in
472    * the associated sequence.
473    * 
474    * @param pos
475    * @return
476    */
477   public int getMappedPosition(int pos)
478   {
479     if (map != null)
480     {
481       int[] mp = map.shiftFrom(pos);
482       if (mp != null)
483       {
484         return mp[0];
485       }
486     }
487     return pos;
488   }
489
490   public int[] getMappedWord(int pos)
491   {
492     if (map != null)
493     {
494       int[] mp = map.shiftFrom(pos);
495       if (mp != null)
496       {
497         return new int[] { mp[0], mp[0] + mp[2] * (map.getToRatio() - 1) };
498       }
499     }
500     return null;
501   }
502
503   /**
504    * locates the region of feature f in the associated sequence's reference
505    * frame
506    * 
507    * @param f
508    * @return one or more features corresponding to f
509    */
510   public SequenceFeature[] locateFeature(SequenceFeature f)
511   {
512     if (true)
513     { // f.getBegin()!=f.getEnd()) {
514       if (map != null)
515       {
516         int[] frange = map.locateInFrom(f.getBegin(), f.getEnd());
517         if (frange == null)
518         {
519           // JBPNote - this isprobably not the right thing to doJBPHack
520           return null;
521         }
522         SequenceFeature[] vf = new SequenceFeature[frange.length / 2];
523         for (int i = 0, v = 0; i < frange.length; i += 2, v++)
524         {
525           vf[v] = new SequenceFeature(f);
526           vf[v].setBegin(frange[i]);
527           vf[v].setEnd(frange[i + 1]);
528           if (frange.length > 2)
529           {
530             vf[v].setDescription(f.getDescription() + "\nPart " + (v + 1));
531           }
532         }
533         return vf;
534       }
535     }
536     if (false) // else
537     {
538       int[] word = getWord(f.getBegin());
539       if (word[0] < word[1])
540       {
541         f.setBegin(word[0]);
542       }
543       else
544       {
545         f.setBegin(word[1]);
546       }
547       word = getWord(f.getEnd());
548       if (word[0] > word[1])
549       {
550         f.setEnd(word[0]);
551       }
552       else
553       {
554         f.setEnd(word[1]);
555       }
556     }
557     // give up and just return the feature.
558     return new SequenceFeature[] { f };
559   }
560
561   /**
562    * return a series of contigs on the associated sequence corresponding to the
563    * from,to interval on the mapped reference frame
564    * 
565    * @param from
566    * @param to
567    * @return int[] { from_i, to_i for i=1 to n contiguous regions in the
568    *         associated sequence}
569    */
570   public int[] locateRange(int from, int to)
571   {
572     if (map != null)
573     {
574       if (from <= to)
575       {
576         from = (map.getToLowest() < from) ? from : map.getToLowest();
577         to = (map.getToHighest() > to) ? to : map.getToHighest();
578         if (from > to)
579         {
580           return null;
581         }
582       }
583       else
584       {
585         from = (map.getToHighest() > from) ? from : map.getToHighest();
586         to = (map.getToLowest() < to) ? to : map.getToLowest();
587         if (from < to)
588         {
589           return null;
590         }
591       }
592       return map.locateInFrom(from, to);
593     }
594     return new int[] { from, to };
595   }
596
597   /**
598    * return a series of mapped contigs mapped from a range on the associated
599    * sequence
600    * 
601    * @param from
602    * @param to
603    * @return
604    */
605   public int[] locateMappedRange(int from, int to)
606   {
607     if (map != null)
608     {
609
610       if (from <= to)
611       {
612         from = (map.getFromLowest() < from) ? from : map.getFromLowest();
613         to = (map.getFromHighest() > to) ? to : map.getFromHighest();
614         if (from > to)
615         {
616           return null;
617         }
618       }
619       else
620       {
621         from = (map.getFromHighest() > from) ? from : map.getFromHighest();
622         to = (map.getFromLowest() < to) ? to : map.getFromLowest();
623         if (from < to)
624         {
625           return null;
626         }
627       }
628       return map.locateInTo(from, to);
629     }
630     return new int[] { from, to };
631   }
632
633   /**
634    * return a new mapping object with a maplist modifed to only map the visible
635    * regions defined by viscontigs.
636    * 
637    * @param viscontigs
638    * @return
639    */
640   public Mapping intersectVisContigs(int[] viscontigs)
641   {
642     Mapping copy = new Mapping(this);
643     if (map != null)
644     {
645       int vpos = 0;
646       int apos = 0;
647       Vector toRange = new Vector();
648       Vector fromRange = new Vector();
649       for (int vc = 0; vc < viscontigs.length; vc += 2)
650       {
651         // find a mapped range in this visible region
652         int[] mpr = locateMappedRange(1 + viscontigs[vc],
653                 viscontigs[vc + 1] - 1);
654         if (mpr != null)
655         {
656           for (int m = 0; m < mpr.length; m += 2)
657           {
658             toRange.addElement(new int[] { mpr[m], mpr[m + 1] });
659             int[] xpos = locateRange(mpr[m], mpr[m + 1]);
660             for (int x = 0; x < xpos.length; x += 2)
661             {
662               fromRange.addElement(new int[] { xpos[x], xpos[x + 1] });
663             }
664           }
665         }
666       }
667       int[] from = new int[fromRange.size() * 2];
668       int[] to = new int[toRange.size() * 2];
669       int[] r;
670       for (int f = 0, fSize = fromRange.size(); f < fSize; f++)
671       {
672         r = (int[]) fromRange.elementAt(f);
673         from[f * 2] = r[0];
674         from[f * 2 + 1] = r[1];
675       }
676       for (int f = 0, fSize = toRange.size(); f < fSize; f++)
677       {
678         r = (int[]) toRange.elementAt(f);
679         to[f * 2] = r[0];
680         to[f * 2 + 1] = r[1];
681       }
682       copy.setMap(new MapList(from, to, map.getFromRatio(), map
683               .getToRatio()));
684     }
685     return copy;
686   }
687
688   /**
689    * get the sequence being mapped to - if any
690    * 
691    * @return null or a dataset sequence
692    */
693   public SequenceI getTo()
694   {
695     return to;
696   }
697
698   /**
699    * set the dataset sequence being mapped to if any
700    * 
701    * @param tto
702    */
703   public void setTo(SequenceI tto)
704   {
705     to = tto;
706   }
707
708   /*
709    * (non-Javadoc)
710    * 
711    * @see java.lang.Object#finalize()
712    */
713   @Override
714   protected void finalize() throws Throwable
715   {
716     map = null;
717     to = null;
718     super.finalize();
719   }
720
721   /**
722    * Returns an iterator which can serve up the aligned codon column positions
723    * and their corresponding peptide products
724    * 
725    * @param seq
726    *          an aligned (i.e. possibly gapped) sequence
727    * @param gapChar
728    * @return
729    */
730   public Iterator<AlignedCodon> getCodonIterator(SequenceI seq, char gapChar)
731   {
732     return new AlignedCodonIterator(seq, gapChar);
733   }
734
735   /**
736    * Readable representation for debugging only, not guaranteed not to change
737    */
738   @Override
739   public String toString()
740   {
741     return String.format("%s %s", this.map.toString(), this.to == null ? ""
742             : this.to.getName());
743   }
744
745 }