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