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