JAL-3303 ignore deletions when computing peptide variants
[jalview.git] / src / jalview / datamodel / MappedFeatures.java
1 package jalview.datamodel;
2
3 import jalview.io.gff.Gff3Helper;
4 import jalview.schemes.ResidueProperties;
5 import jalview.util.MappingUtils;
6 import jalview.util.StringUtils;
7
8 import java.util.ArrayList;
9 import java.util.List;
10
11 /**
12  * A data bean to hold a list of mapped sequence features (e.g. CDS features
13  * mapped from protein), and the mapping between the sequences
14  * 
15  * @author gmcarstairs
16  */
17 public class MappedFeatures
18 {
19   /*
20    * the mapping from CDS to peptide
21    */
22   public final Mapping mapping;
23
24   /**
25    * the CDS sequence mapped to
26    */
27   public final SequenceI fromSeq;
28
29   /*
30    * the residue position in the peptide sequence
31    */
32   public final int fromPosition;
33
34   /*
35    * the peptide residue at the position 
36    */
37   public final char fromResidue;
38
39   /*
40    * features on CDS that overlap the codon positions
41    */
42   public final List<SequenceFeature> features;
43
44   /**
45    * Constructor
46    * 
47    * @param theMapping
48    * @param pos
49    * @param res
50    * @param theFeatures
51    */
52   public MappedFeatures(Mapping theMapping, SequenceI from, int pos,
53           char res,
54           List<SequenceFeature> theFeatures)
55   {
56     mapping = theMapping;
57     fromSeq = from;
58     fromPosition = pos;
59     fromResidue = res;
60     features = theFeatures;
61   }
62
63   /**
64    * Computes and returns a (possibly empty) list of HGVS notation peptide
65    * variants derived from codon allele variants
66    * 
67    * @return
68    */
69   public List<String> findProteinVariants()
70   {
71     List<String> vars = new ArrayList<>();
72     if (features.isEmpty())
73     {
74       return vars;
75     }
76
77     /*
78      * determine canonical codon
79      */
80     int[] codonPos = MappingUtils.flattenRanges(
81             mapping.getMap().locateInFrom(fromPosition, fromPosition));
82     if (codonPos.length != 3)
83     {
84       // error
85       return vars;
86     }
87     final char[] baseCodon = new char[3];
88     int cdsStart = fromSeq.getStart();
89     baseCodon[0] = fromSeq.getCharAt(codonPos[0] - cdsStart);
90     baseCodon[1] = fromSeq.getCharAt(codonPos[1] - cdsStart);
91     baseCodon[2] = fromSeq.getCharAt(codonPos[2] - cdsStart);
92
93     for (SequenceFeature sf : features)
94     {
95       /*
96        * VCF data may already contain the protein consequence
97        */
98       String hgvsp = sf.getValueAsString("CSQ", "HGVSp");
99       if (hgvsp != null)
100       {
101         int colonPos = hgvsp.indexOf(':');
102         if (colonPos >= 0)
103         {
104           String var = hgvsp.substring(colonPos + 1);
105           if (!vars.contains(var))
106           {
107             vars.add(var);
108           }
109           continue;
110         }
111       }
112
113       /*
114        * otherwise, compute codon and peptide variant
115        */
116       // todo avoid duplication of code in AlignmentUtils.buildDnaVariantsMap
117       int cdsPos = sf.getBegin();
118       if (cdsPos != sf.getEnd())
119       {
120         // not handling multi-locus variant features
121         continue;
122       }
123       if (cdsPos != codonPos[0] && cdsPos != codonPos[1]
124               && cdsPos != codonPos[2])
125       {
126         // e.g. feature on intron within spliced codon!
127         continue;
128       }
129
130       String alls = (String) sf.getValue(Gff3Helper.ALLELES);
131       if (alls == null)
132       {
133         continue;
134       }
135       String from3 = StringUtils.toSentenceCase(
136               ResidueProperties.aa2Triplet
137                       .get(String.valueOf(fromResidue)));
138
139       /*
140        * make a peptide variant for each SNP allele 
141        * e.g. C,G,T gives variants G and T for base C
142        */
143       String[] alleles = alls.toUpperCase().split(",");
144       for (String allele : alleles)
145       {
146         allele = allele.trim().toUpperCase();
147         if (allele.length() > 1 || "-".equals(allele))
148         {
149           continue; // multi-locus variant
150         }
151         char[] variantCodon = new char[3];
152         variantCodon[0] = baseCodon[0];
153         variantCodon[1] = baseCodon[1];
154         variantCodon[2] = baseCodon[2];
155
156         /*
157          * poke variant base into canonical codon
158          */
159         int i = cdsPos == codonPos[0] ? 0 : (cdsPos == codonPos[1] ? 1 : 2);
160         variantCodon[i] = allele.toUpperCase().charAt(0);
161         String codon = new String(variantCodon);
162         String peptide = ResidueProperties.codonTranslate(codon);
163         if (fromResidue != peptide.charAt(0))
164         {
165           String to3 = ResidueProperties.STOP.equals(peptide) ? "STOP"
166                   : StringUtils.toSentenceCase(
167                   ResidueProperties.aa2Triplet.get(peptide));
168           String var = "p." + from3 + fromPosition + to3;
169           if (!vars.contains(var))
170           {
171             vars.add(var);
172           }
173         }
174       }
175     }
176
177     return vars;
178   }
179 }