JAL-1841, bug fix in rna SS consensus, additions to Rna[Test]
[jalview.git] / src / jalview / analysis / Rna.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 /* Author: Lauren Michelle Lui 
22  * Methods are based on RALEE methods http://personalpages.manchester.ac.uk/staff/sam.griffiths-jones/software/ralee/
23  * Additional Author: Jan Engelhart (2011) - Structure consensus and bug fixing
24  * Additional Author: Anne Menard (2012) - Pseudoknot support and secondary structure consensus
25  * */
26
27 package jalview.analysis;
28
29 import jalview.analysis.SecStrConsensus.SimpleBP;
30 import jalview.datamodel.SequenceFeature;
31 import jalview.util.MessageManager;
32
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashSet;
36 import java.util.Hashtable;
37 import java.util.List;
38 import java.util.Stack;
39 import java.util.Vector;
40
41 public class Rna
42 {
43
44   static Hashtable<Integer, Integer> pairHash = new Hashtable<Integer, Integer>();
45
46   private static final Character[] openingPars = { '(', '[', '{', '<', 'A',
47       'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
48       'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
49
50   private static final Character[] closingPars = { ')', ']', '}', '>', 'a',
51       'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
52       'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
53
54   private static HashSet<Character> openingParsSet = new HashSet<Character>(
55           Arrays.asList(openingPars));
56
57   private static HashSet<Character> closingParsSet = new HashSet<Character>(
58           Arrays.asList(closingPars));
59
60   private static Hashtable<Character, Character> closingToOpening = new Hashtable<Character, Character>()
61   // Initializing final data structure
62   {
63     private static final long serialVersionUID = 1L;
64     {
65       for (int i = 0; i < openingPars.length; i++)
66       {
67         // System.out.println(closingPars[i] + "->" + openingPars[i]);
68         put(closingPars[i], openingPars[i]);
69       }
70     }
71   };
72
73   public static boolean isOpeningParenthesis(char c)
74   {
75     return openingParsSet.contains(c);
76   }
77
78   public static boolean isClosingParenthesis(char c)
79   {
80     return closingParsSet.contains(c);
81   }
82
83   private static char matchingOpeningParenthesis(char closingParenthesis)
84           throws WUSSParseException
85   {
86     if (!isClosingParenthesis(closingParenthesis))
87     {
88       throw new WUSSParseException(
89               MessageManager.formatMessage(
90                       "exception.querying_matching_opening_parenthesis_for_non_closing_parenthesis",
91                       new String[] { String.valueOf(closingParenthesis) }),
92               -1);
93     }
94
95     return closingToOpening.get(closingParenthesis);
96   }
97
98   /**
99    * Based off of RALEE code ralee-get-base-pairs. Keeps track of open bracket
100    * positions in "stack" vector. When a close bracket is reached, pair this
101    * with the last matching element in the "stack" vector and store in "pairs"
102    * vector. Remove last element in the "stack" vector. Continue in this manner
103    * until the whole string is processed. Parse errors are thrown as exceptions
104    * wrapping the error location - position of the first unmatched closing
105    * bracket, or string length if there is an unmatched opening bracket.
106    * 
107    * @param line
108    *          Secondary structure line of an RNA Stockholm file
109    * @return
110    * @throw {@link WUSSParseException}
111    */
112   public static Vector<SimpleBP> getSimpleBPs(CharSequence line)
113           throws WUSSParseException
114   {
115     Hashtable<Character, Stack<Integer>> stacks = new Hashtable<Character, Stack<Integer>>();
116     Vector<SimpleBP> pairs = new Vector<SimpleBP>();
117     int i = 0;
118     while (i < line.length())
119     {
120       char base = line.charAt(i);
121
122       if (isOpeningParenthesis(base))
123       {
124         if (!stacks.containsKey(base))
125         {
126           stacks.put(base, new Stack<Integer>());
127         }
128         stacks.get(base).push(i);
129
130       }
131       else if (isClosingParenthesis(base))
132       {
133
134         char opening = matchingOpeningParenthesis(base);
135
136         if (!stacks.containsKey(opening))
137         {
138           throw new WUSSParseException(MessageManager.formatMessage(
139                   "exception.mismatched_unseen_closing_char",
140                   new String[] { String.valueOf(base) }), i);
141         }
142
143         Stack<Integer> stack = stacks.get(opening);
144         if (stack.isEmpty())
145         {
146           // error whilst parsing i'th position. pass back
147           throw new WUSSParseException(MessageManager.formatMessage(
148                   "exception.mismatched_closing_char",
149                   new String[] { String.valueOf(base) }), i);
150         }
151         int temp = stack.pop();
152
153         pairs.add(new SimpleBP(temp, i));
154       }
155       i++;
156     }
157     for (char opening : stacks.keySet())
158     {
159       Stack<Integer> stack = stacks.get(opening);
160       if (!stack.empty())
161       {
162         /*
163          * we have an unmatched opening bracket; report error as at
164          * i (length of input string)
165          */
166         throw new WUSSParseException(MessageManager.formatMessage(
167                 "exception.mismatched_opening_char",
168                 new String[] { String.valueOf(opening),
169                     String.valueOf(stack.pop()) }), i);
170       }
171     }
172     return pairs;
173   }
174
175   public static SequenceFeature[] getBasePairs(List<SimpleBP> bps)
176           throws WUSSParseException
177   {
178     SequenceFeature[] outPairs = new SequenceFeature[bps.size()];
179     for (int p = 0; p < bps.size(); p++)
180     {
181       SimpleBP bp = bps.get(p);
182       outPairs[p] = new SequenceFeature("RNA helix", "", "", bp.getBP5(),
183               bp.getBP3(), "");
184     }
185     return outPairs;
186   }
187
188   public static List<SimpleBP> getModeleBP(CharSequence line)
189           throws WUSSParseException
190   {
191     Vector<SimpleBP> bps = getSimpleBPs(line);
192     return new ArrayList<SimpleBP>(bps);
193   }
194
195   /**
196    * Function to get the end position corresponding to a given start position
197    * 
198    * @param indice
199    *          - start position of a base pair
200    * @return - end position of a base pair
201    */
202   /*
203    * makes no sense at the moment :( public int findEnd(int indice){ //TODO:
204    * Probably extend this to find the start to a given end? //could be done by
205    * putting everything twice to the hash ArrayList<Integer> pair = new
206    * ArrayList<Integer>(); return pairHash.get(indice); }
207    */
208
209   /**
210    * Figures out which helix each position belongs to and stores the helix
211    * number in the 'featureGroup' member of a SequenceFeature Based off of RALEE
212    * code ralee-helix-map.
213    * 
214    * @param pairs
215    *          Array of SequenceFeature (output from Rna.GetBasePairs)
216    */
217   public static void HelixMap(SequenceFeature[] pairs)
218   {
219
220     int helix = 0; // Number of helices/current helix
221     int lastopen = 0; // Position of last open bracket reviewed
222     int lastclose = 9999999; // Position of last close bracket reviewed
223     int i = pairs.length; // Number of pairs
224
225     int open; // Position of an open bracket under review
226     int close; // Position of a close bracket under review
227     int j; // Counter
228
229     Hashtable<Integer, Integer> helices = new Hashtable<Integer, Integer>();
230     // Keep track of helix number for each position
231
232     // Go through each base pair and assign positions a helix
233     for (i = 0; i < pairs.length; i++)
234     {
235
236       open = pairs[i].getBegin();
237       close = pairs[i].getEnd();
238
239       // System.out.println("open " + open + " close " + close);
240       // System.out.println("lastclose " + lastclose + " lastopen " + lastopen);
241
242       // we're moving from right to left based on closing pair
243       /*
244        * catch things like <<..>>..<<..>> |
245        */
246       if (open > lastclose)
247       {
248         helix++;
249       }
250
251       /*
252        * catch things like <<..<<..>>..<<..>>>> |
253        */
254       j = pairs.length - 1;
255       while (j >= 0)
256       {
257         int popen = pairs[j].getBegin();
258
259         // System.out.println("j " + j + " popen " + popen + " lastopen "
260         // +lastopen + " open " + open);
261         if ((popen < lastopen) && (popen > open))
262         {
263           if (helices.containsValue(popen)
264                   && ((helices.get(popen)) == helix))
265           {
266             continue;
267           }
268           else
269           {
270             helix++;
271             break;
272           }
273         }
274
275         j -= 1;
276       }
277
278       // Put positions and helix information into the hashtable
279       helices.put(open, helix);
280       helices.put(close, helix);
281
282       // Record helix as featuregroup
283       pairs[i].setFeatureGroup(Integer.toString(helix));
284
285       lastopen = open;
286       lastclose = close;
287
288     }
289   }
290
291   /**
292    * Answers true if the character is a recognised symbol for RNA secondary
293    * structure. Currently accepts a-z, A-Z, ()[]{}<>.
294    * 
295    * @param c
296    * @return
297    */
298   public static boolean isRnaSecondaryStructureSymbol(char c)
299   {
300     return isOpeningParenthesis(c) || isClosingParenthesis(c);
301   }
302
303   /**
304    * Translates a string to RNA secondary structure representation. Returns the
305    * string with any non-SS characters changed to spaces. Accepted characters
306    * are a-z, A-Z, and (){}[]<> brackets.
307    * 
308    * @param ssString
309    * @return
310    */
311   public static String getRNASecStrucState(String ssString)
312   {
313     if (ssString == null)
314     {
315       return null;
316     }
317     StringBuilder result = new StringBuilder(ssString.length());
318     for (int i = 0; i < ssString.length(); i++)
319     {
320       char c = ssString.charAt(i);
321       result.append(isRnaSecondaryStructureSymbol(c) ? c : " ");
322     }
323     return result.toString();
324   }
325
326   /**
327    * Answers true if the base-pair is either a canonical (A-T/U, C-G) or a
328    * wobble (G-T/U) pair (either way round), else false
329    * 
330    * @param first
331    * @param second
332    * @return
333    */
334   public static boolean isCanonicalOrWobblePair(char first, char second)
335   {
336     if (first > 'Z')
337     {
338       first -= 32;
339     }
340     if (second > 'Z')
341     {
342       second -= 32;
343     }
344   
345     switch (first)
346     {
347     case 'A':
348       switch (second)
349       {
350       case 'T':
351       case 'U':
352         return true;
353       }
354       break;
355     case 'C':
356       switch (second)
357       {
358       case 'G':
359         return true;
360       }
361       break;
362     case 'T':
363     case 'U':
364       switch (second)
365       {
366       case 'A':
367       case 'G':
368         return true;
369       }
370       break;
371     case 'G':
372       switch (second)
373       {
374       case 'C':
375       case 'T':
376       case 'U':
377         return true;
378       }
379       break;
380     }
381     return false;
382   }
383 }