JAL-1925 update source version in license
[jalview.git] / src / jalview / datamodel / Mapping.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9.0b2)
3  * Copyright (C) 2015 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       return new AlignedCodon(alignedCodon[0], alignedCodon[1],
159               alignedCodon[2], peptide);
160     }
161
162     /**
163      * Retrieve the translation as the 'mapped to' position in the mapped to
164      * sequence.
165      * 
166      * @return
167      */
168     private String getPeptide()
169     {
170       // TODO should ideally handle toRatio other than 1 as well...
171       // i.e. code like getNextCodon()
172       if (toPosition <= currentToRange[1])
173       {
174         SequenceI seq = Mapping.this.to;
175         char pep = seq.getSequence()[toPosition - seq.getStart()];
176         toPosition++;
177         return String.valueOf(pep);
178       }
179       if (!toRanges.hasNext())
180       {
181         throw new NoSuchElementException("Ran out of peptide at position "
182                 + toPosition);
183       }
184       currentToRange = toRanges.next();
185       toPosition = currentToRange[0];
186       return getPeptide();
187     }
188
189     /**
190      * Get the (base 1) dataset positions for the next codon in the mapping.
191      * 
192      * @throws IncompleteCodonException
193      *           if less than 3 remaining bases are mapped
194      */
195     private int[] getNextCodon()
196     {
197       int[] codon = new int[3];
198       int codonbase = 0;
199
200       while (codonbase < 3)
201       {
202         if (fromPosition <= currentFromRange[1])
203         {
204           /*
205            * Add next position from the current start-end range
206            */
207           codon[codonbase++] = fromPosition++;
208         }
209         else
210         {
211           /*
212            * Move to the next range - if there is one
213            */
214           if (!fromRanges.hasNext())
215           {
216             throw new IncompleteCodonException();
217           }
218           currentFromRange = fromRanges.next();
219           fromPosition = currentFromRange[0];
220         }
221       }
222       return codon;
223     }
224
225     /**
226      * Get the aligned column positions (base 0) for the given sequence
227      * positions (base 1), by counting ungapped characters in the aligned
228      * sequence.
229      * 
230      * @param codon
231      * @return
232      */
233     private int[] getAlignedCodon(int[] codon)
234     {
235       int[] aligned = new int[codon.length];
236       for (int i = 0; i < codon.length; i++)
237       {
238         aligned[i] = getAlignedColumn(codon[i]);
239       }
240       return aligned;
241     }
242
243     /**
244      * Get the aligned column position (base 0) for the given sequence position
245      * (base 1).
246      * 
247      * @param sequencePos
248      * @return
249      */
250     private int getAlignedColumn(int sequencePos)
251     {
252       /*
253        * allow for offset e.g. treat pos 8 as 2 if sequence starts at 7
254        */
255       int truePos = sequencePos - (start - 1);
256       while (alignedBases < truePos && alignedColumn < alignedSeq.length)
257       {
258         if (alignedSeq[alignedColumn++] != gap)
259         {
260           alignedBases++;
261         }
262       }
263       return alignedColumn - 1;
264     }
265
266     @Override
267     public void remove()
268     {
269       // ignore
270     }
271
272   }
273
274   /**
275    * Contains the start-end pairs mapping from the associated sequence to the
276    * sequence in the database coordinate system. It also takes care of step
277    * difference between coordinate systems.
278    */
279   MapList map = null;
280
281   /**
282    * The sequence that map maps the associated sequence to (if any).
283    */
284   SequenceI to = null;
285
286   public Mapping(MapList map)
287   {
288     super();
289     this.map = map;
290   }
291
292   public Mapping(SequenceI to, MapList map)
293   {
294     this(map);
295     this.to = to;
296   }
297
298   /**
299    * create a new mapping from
300    * 
301    * @param to
302    *          the sequence being mapped
303    * @param exon
304    *          int[] {start,end,start,end} series on associated sequence
305    * @param is
306    *          int[] {start,end,...} ranges on the reference frame being mapped
307    *          to
308    * @param i
309    *          step size on associated sequence
310    * @param j
311    *          step size on mapped frame
312    */
313   public Mapping(SequenceI to, int[] exon, int[] is, int i, int j)
314   {
315     this(to, new MapList(exon, is, i, j));
316   }
317
318   /**
319    * create a duplicate (and independent) mapping object with the same reference
320    * to any SequenceI being mapped to.
321    * 
322    * @param map2
323    */
324   public Mapping(Mapping map2)
325   {
326     if (map2 != this && map2 != null)
327     {
328       if (map2.map != null)
329       {
330         map = new MapList(map2.map);
331       }
332       to = map2.to;
333     }
334   }
335
336   /**
337    * @return the map
338    */
339   public MapList getMap()
340   {
341     return map;
342   }
343
344   /**
345    * @param map
346    *          the map to set
347    */
348   public void setMap(MapList map)
349   {
350     this.map = map;
351   }
352
353   /**
354    * Equals that compares both the to references and MapList mappings.
355    * 
356    * @param other
357    * @return
358    */
359   @Override
360   public boolean equals(Object o)
361   {
362     // TODO should override Object.hashCode() to ensure that equal objects have
363     // equal hashcodes
364     if (o == null || !(o instanceof Mapping))
365     {
366       return false;
367     }
368     Mapping other = (Mapping) o;
369     if (other == this)
370     {
371       return true;
372     }
373     if (other.to != to)
374     {
375       return false;
376     }
377     if ((map != null && other.map == null)
378             || (map == null && other.map != null))
379     {
380       return false;
381     }
382     if ((map == null && other.map == null) || map.equals(other.map))
383     {
384       return true;
385     }
386     return false;
387   }
388
389   /**
390    * get the 'initial' position in the associated sequence for a position in the
391    * mapped reference frame
392    * 
393    * @param mpos
394    * @return
395    */
396   public int getPosition(int mpos)
397   {
398     if (map != null)
399     {
400       int[] mp = map.shiftTo(mpos);
401       if (mp != null)
402       {
403         return mp[0];
404       }
405     }
406     return mpos;
407   }
408
409   /**
410    * gets boundary in direction of mapping
411    * 
412    * @param position
413    *          in mapped reference frame
414    * @return int{start, end} positions in associated sequence (in direction of
415    *         mapped word)
416    */
417   public int[] getWord(int mpos)
418   {
419     if (map != null)
420     {
421       return map.getToWord(mpos);
422     }
423     return null;
424   }
425
426   /**
427    * width of mapped unit in associated sequence
428    * 
429    */
430   public int getWidth()
431   {
432     if (map != null)
433     {
434       return map.getFromRatio();
435     }
436     return 1;
437   }
438
439   /**
440    * width of unit in mapped reference frame
441    * 
442    * @return
443    */
444   public int getMappedWidth()
445   {
446     if (map != null)
447     {
448       return map.getToRatio();
449     }
450     return 1;
451   }
452
453   /**
454    * get mapped position in the associated reference frame for position pos in
455    * the associated sequence.
456    * 
457    * @param pos
458    * @return
459    */
460   public int getMappedPosition(int pos)
461   {
462     if (map != null)
463     {
464       int[] mp = map.shiftFrom(pos);
465       if (mp != null)
466       {
467         return mp[0];
468       }
469     }
470     return pos;
471   }
472
473   public int[] getMappedWord(int pos)
474   {
475     if (map != null)
476     {
477       int[] mp = map.shiftFrom(pos);
478       if (mp != null)
479       {
480         return new int[] { mp[0], mp[0] + mp[2] * (map.getToRatio() - 1) };
481       }
482     }
483     return null;
484   }
485
486   /**
487    * locates the region of feature f in the associated sequence's reference
488    * frame
489    * 
490    * @param f
491    * @return one or more features corresponding to f
492    */
493   public SequenceFeature[] locateFeature(SequenceFeature f)
494   {
495     if (true)
496     { // f.getBegin()!=f.getEnd()) {
497       if (map != null)
498       {
499         int[] frange = map.locateInFrom(f.getBegin(), f.getEnd());
500         if (frange == null)
501         {
502           // JBPNote - this isprobably not the right thing to doJBPHack
503           return null;
504         }
505         SequenceFeature[] vf = new SequenceFeature[frange.length / 2];
506         for (int i = 0, v = 0; i < frange.length; i += 2, v++)
507         {
508           vf[v] = new SequenceFeature(f);
509           vf[v].setBegin(frange[i]);
510           vf[v].setEnd(frange[i + 1]);
511           if (frange.length > 2)
512           {
513             vf[v].setDescription(f.getDescription() + "\nPart " + (v + 1));
514           }
515         }
516         return vf;
517       }
518     }
519     if (false) // else
520     {
521       int[] word = getWord(f.getBegin());
522       if (word[0] < word[1])
523       {
524         f.setBegin(word[0]);
525       }
526       else
527       {
528         f.setBegin(word[1]);
529       }
530       word = getWord(f.getEnd());
531       if (word[0] > word[1])
532       {
533         f.setEnd(word[0]);
534       }
535       else
536       {
537         f.setEnd(word[1]);
538       }
539     }
540     // give up and just return the feature.
541     return new SequenceFeature[] { f };
542   }
543
544   /**
545    * return a series of contigs on the associated sequence corresponding to the
546    * from,to interval on the mapped reference frame
547    * 
548    * @param from
549    * @param to
550    * @return int[] { from_i, to_i for i=1 to n contiguous regions in the
551    *         associated sequence}
552    */
553   public int[] locateRange(int from, int to)
554   {
555     if (map != null)
556     {
557       if (from <= to)
558       {
559         from = (map.getToLowest() < from) ? from : map.getToLowest();
560         to = (map.getToHighest() > to) ? to : map.getToHighest();
561         if (from > to)
562         {
563           return null;
564         }
565       }
566       else
567       {
568         from = (map.getToHighest() > from) ? from : map.getToHighest();
569         to = (map.getToLowest() < to) ? to : map.getToLowest();
570         if (from < to)
571         {
572           return null;
573         }
574       }
575       return map.locateInFrom(from, to);
576     }
577     return new int[] { from, to };
578   }
579
580   /**
581    * return a series of mapped contigs mapped from a range on the associated
582    * sequence
583    * 
584    * @param from
585    * @param to
586    * @return
587    */
588   public int[] locateMappedRange(int from, int to)
589   {
590     if (map != null)
591     {
592
593       if (from <= to)
594       {
595         from = (map.getFromLowest() < from) ? from : map.getFromLowest();
596         to = (map.getFromHighest() > to) ? to : map.getFromHighest();
597         if (from > to)
598         {
599           return null;
600         }
601       }
602       else
603       {
604         from = (map.getFromHighest() > from) ? from : map.getFromHighest();
605         to = (map.getFromLowest() < to) ? to : map.getFromLowest();
606         if (from < to)
607         {
608           return null;
609         }
610       }
611       return map.locateInTo(from, to);
612     }
613     return new int[] { from, to };
614   }
615
616   /**
617    * return a new mapping object with a maplist modifed to only map the visible
618    * regions defined by viscontigs.
619    * 
620    * @param viscontigs
621    * @return
622    */
623   public Mapping intersectVisContigs(int[] viscontigs)
624   {
625     Mapping copy = new Mapping(this);
626     if (map != null)
627     {
628       int vpos = 0;
629       int apos = 0;
630       Vector toRange = new Vector();
631       Vector fromRange = new Vector();
632       for (int vc = 0; vc < viscontigs.length; vc += 2)
633       {
634         // find a mapped range in this visible region
635         int[] mpr = locateMappedRange(1 + viscontigs[vc],
636                 viscontigs[vc + 1] - 1);
637         if (mpr != null)
638         {
639           for (int m = 0; m < mpr.length; m += 2)
640           {
641             toRange.addElement(new int[] { mpr[m], mpr[m + 1] });
642             int[] xpos = locateRange(mpr[m], mpr[m + 1]);
643             for (int x = 0; x < xpos.length; x += 2)
644             {
645               fromRange.addElement(new int[] { xpos[x], xpos[x + 1] });
646             }
647           }
648         }
649       }
650       int[] from = new int[fromRange.size() * 2];
651       int[] to = new int[toRange.size() * 2];
652       int[] r;
653       for (int f = 0, fSize = fromRange.size(); f < fSize; f++)
654       {
655         r = (int[]) fromRange.elementAt(f);
656         from[f * 2] = r[0];
657         from[f * 2 + 1] = r[1];
658       }
659       for (int f = 0, fSize = toRange.size(); f < fSize; f++)
660       {
661         r = (int[]) toRange.elementAt(f);
662         to[f * 2] = r[0];
663         to[f * 2 + 1] = r[1];
664       }
665       copy.setMap(new MapList(from, to, map.getFromRatio(), map
666               .getToRatio()));
667     }
668     return copy;
669   }
670
671   /**
672    * get the sequence being mapped to - if any
673    * 
674    * @return null or a dataset sequence
675    */
676   public SequenceI getTo()
677   {
678     return to;
679   }
680
681   /**
682    * set the dataset sequence being mapped to if any
683    * 
684    * @param tto
685    */
686   public void setTo(SequenceI tto)
687   {
688     to = tto;
689   }
690
691   /*
692    * (non-Javadoc)
693    * 
694    * @see java.lang.Object#finalize()
695    */
696   protected void finalize() throws Throwable
697   {
698     map = null;
699     to = null;
700     super.finalize();
701   }
702
703   public Iterator<AlignedCodon> getCodonIterator(SequenceI seq, char gapChar)
704   {
705     return new AlignedCodonIterator(seq, gapChar);
706   }
707
708 }