AAConWS is working
[jabaws.git] / datamodel / compbio / data / sequence / SequenceUtil.java
1 /*\r
2  * @(#)SequenceUtil.java 1.0 September 2009 Copyright (c) 2009 Peter Troshin\r
3  * Jalview Web Services version: 2.0 This library is free software; you can\r
4  * redistribute it and/or modify it under the terms of the Apache License\r
5  * version 2 as published by the Apache Software Foundation This library is\r
6  * distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;\r
7  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A\r
8  * PARTICULAR PURPOSE. See the Apache License for more details. A copy of the\r
9  * license is in apache_license.txt. It is also available here: see:\r
10  * http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or derived\r
11  * work distributed in source code form must include this copyright and license\r
12  * notice.\r
13  */\r
14 \r
15 package compbio.data.sequence;\r
16 \r
17 import java.io.BufferedReader;\r
18 import java.io.BufferedWriter;\r
19 import java.io.Closeable;\r
20 import java.io.File;\r
21 import java.io.FileInputStream;\r
22 import java.io.IOException;\r
23 import java.io.InputStream;\r
24 import java.io.InputStreamReader;\r
25 import java.io.OutputStream;\r
26 import java.io.OutputStreamWriter;\r
27 import java.util.ArrayList;\r
28 import java.util.HashSet;\r
29 import java.util.List;\r
30 import java.util.Scanner;\r
31 import java.util.logging.Level;\r
32 import java.util.regex.Matcher;\r
33 import java.util.regex.Pattern;\r
34 \r
35 import compbio.conservation.Method;\r
36 \r
37 /**\r
38  * Utility class for operations on sequences\r
39  * \r
40  * @author Petr Troshin\r
41  * @version 1.0\r
42  */\r
43 public final class SequenceUtil {\r
44 \r
45         /**\r
46          * A whitespace character: [\t\n\x0B\f\r]\r
47          */\r
48         public static final Pattern WHITE_SPACE = Pattern.compile("\\s");\r
49 \r
50         /**\r
51          * A digit\r
52          */\r
53         public static final Pattern DIGIT = Pattern.compile("\\d");\r
54 \r
55         /**\r
56          * Non word\r
57          */\r
58         public static final Pattern NONWORD = Pattern.compile("\\W");\r
59 \r
60         /**\r
61          * Valid Amino acids\r
62          */\r
63         public static final Pattern AA = Pattern.compile("[ARNDCQEGHILKMFPSTWYV]+",\r
64                         Pattern.CASE_INSENSITIVE);\r
65 \r
66         /**\r
67          * inversion of AA pattern\r
68          */\r
69         public static final Pattern NON_AA = Pattern.compile(\r
70                         "[^ARNDCQEGHILKMFPSTWYV]+", Pattern.CASE_INSENSITIVE);\r
71 \r
72         /**\r
73          * Same as AA pattern but with two additional letters - XU\r
74          */\r
75         public static final Pattern AMBIGUOUS_AA = Pattern.compile(\r
76                         "[ARNDCQEGHILKMFPSTWYVXU]+", Pattern.CASE_INSENSITIVE);\r
77 \r
78         /**\r
79          * Nucleotides a, t, g, c, u\r
80          */\r
81         public static final Pattern NUCLEOTIDE = Pattern.compile("[AGTCU]+",\r
82                         Pattern.CASE_INSENSITIVE);\r
83 \r
84         /**\r
85          * Ambiguous nucleotide\r
86          */\r
87         public static final Pattern AMBIGUOUS_NUCLEOTIDE = Pattern.compile(\r
88                         "[AGTCRYMKSWHBVDNU]+", Pattern.CASE_INSENSITIVE); // see IUPAC\r
89         /**\r
90          * Non nucleotide\r
91          */\r
92         public static final Pattern NON_NUCLEOTIDE = Pattern.compile("[^AGTCU]+",\r
93                         Pattern.CASE_INSENSITIVE);\r
94 \r
95         private SequenceUtil() {\r
96         } // utility class, no instantiation\r
97 \r
98         /*\r
99          * public static void write_PirSeq(OutputStream os, FastaSequence seq)\r
100          * throws IOException { BufferedWriter pir_out = new BufferedWriter(new\r
101          * OutputStreamWriter(os)); pir_out.write(">P1;" + seq.getId() +\r
102          * SysPrefs.newlinechar); pir_out.write(seq.getSequence() +\r
103          * SysPrefs.newlinechar); pir_out.close(); } public static void\r
104          * write_FastaSeq(OutputStream os, FastaSequence seq) throws IOException {\r
105          * BufferedWriter fasta_out = new BufferedWriter( new\r
106          * OutputStreamWriter(os)); fasta_out.write(">" + seq.getId() +\r
107          * SysPrefs.newlinechar); fasta_out.write(seq.getSequence() +\r
108          * SysPrefs.newlinechar); fasta_out.close(); }\r
109          */\r
110 \r
111         /**\r
112          * @return true is the sequence contains only letters a,c, t, g, u\r
113          */\r
114         public static boolean isNucleotideSequence(final FastaSequence s) {\r
115                 return SequenceUtil.isNonAmbNucleotideSequence(s.getSequence());\r
116         }\r
117 \r
118         /**\r
119          * Ambiguous DNA chars : AGTCRYMKSWHBVDN // differs from protein in only one\r
120          * (!) - B char\r
121          */\r
122         public static boolean isNonAmbNucleotideSequence(String sequence) {\r
123                 sequence = SequenceUtil.cleanSequence(sequence);\r
124                 if (SequenceUtil.DIGIT.matcher(sequence).find()) {\r
125                         return false;\r
126                 }\r
127                 if (SequenceUtil.NON_NUCLEOTIDE.matcher(sequence).find()) {\r
128                         return false;\r
129                         /*\r
130                          * System.out.format("I found the text starting at " +\r
131                          * "index %d and ending at index %d.%n", nonDNAmatcher .start(),\r
132                          * nonDNAmatcher.end());\r
133                          */\r
134                 }\r
135                 final Matcher DNAmatcher = SequenceUtil.NUCLEOTIDE.matcher(sequence);\r
136                 return DNAmatcher.find();\r
137         }\r
138 \r
139         /**\r
140          * Removes all whitespace chars in the sequence string\r
141          * \r
142          * @param sequence\r
143          * @return cleaned up sequence\r
144          */\r
145         public static String cleanSequence(String sequence) {\r
146                 assert sequence != null;\r
147                 final Matcher m = SequenceUtil.WHITE_SPACE.matcher(sequence);\r
148                 sequence = m.replaceAll("").toUpperCase();\r
149                 return sequence;\r
150         }\r
151 \r
152         /**\r
153          * Removes all special characters and digits as well as whitespace chars\r
154          * from the sequence\r
155          * \r
156          * @param sequence\r
157          * @return cleaned up sequence\r
158          */\r
159         public static String deepCleanSequence(String sequence) {\r
160                 sequence = SequenceUtil.cleanSequence(sequence);\r
161                 sequence = SequenceUtil.DIGIT.matcher(sequence).replaceAll("");\r
162                 sequence = SequenceUtil.NONWORD.matcher(sequence).replaceAll("");\r
163                 final Pattern othernonSeqChars = Pattern.compile("[_-]+");\r
164                 sequence = othernonSeqChars.matcher(sequence).replaceAll("");\r
165                 return sequence;\r
166         }\r
167 \r
168         /**\r
169          * @param sequence\r
170          * @return true is the sequence is a protein sequence, false overwise\r
171          */\r
172         public static boolean isProteinSequence(String sequence) {\r
173                 sequence = SequenceUtil.cleanSequence(sequence);\r
174                 if (SequenceUtil.isNonAmbNucleotideSequence(sequence)) {\r
175                         return false;\r
176                 }\r
177                 if (SequenceUtil.DIGIT.matcher(sequence).find()) {\r
178                         return false;\r
179                 }\r
180                 if (SequenceUtil.NON_AA.matcher(sequence).find()) {\r
181                         return false;\r
182                 }\r
183                 final Matcher protmatcher = SequenceUtil.AA.matcher(sequence);\r
184                 return protmatcher.find();\r
185         }\r
186 \r
187         /**\r
188          * Check whether the sequence confirms to amboguous protein sequence\r
189          * \r
190          * @param sequence\r
191          * @return return true only if the sequence if ambiguous protein sequence\r
192          *         Return false otherwise. e.g. if the sequence is non-ambiguous\r
193          *         protein or DNA\r
194          */\r
195         public static boolean isAmbiguosProtein(String sequence) {\r
196                 sequence = SequenceUtil.cleanSequence(sequence);\r
197                 if (SequenceUtil.isNonAmbNucleotideSequence(sequence)) {\r
198                         return false;\r
199                 }\r
200                 if (SequenceUtil.DIGIT.matcher(sequence).find()) {\r
201                         return false;\r
202                 }\r
203                 if (SequenceUtil.NON_AA.matcher(sequence).find()) {\r
204                         return false;\r
205                 }\r
206                 if (SequenceUtil.AA.matcher(sequence).find()) {\r
207                         return false;\r
208                 }\r
209                 final Matcher amb_prot = SequenceUtil.AMBIGUOUS_AA.matcher(sequence);\r
210                 return amb_prot.find();\r
211         }\r
212 \r
213         /**\r
214          * Writes list of FastaSequeces into the outstream formatting the sequence\r
215          * so that it contains width chars on each line\r
216          * \r
217          * @param outstream\r
218          * @param sequences\r
219          * @param width\r
220          *            - the maximum number of characters to write in one line\r
221          * @throws IOException\r
222          */\r
223         public static void writeFasta(final OutputStream outstream,\r
224                         final List<FastaSequence> sequences, final int width)\r
225                         throws IOException {\r
226                 writeFastaKeepTheStream(outstream, sequences, width);\r
227                 outstream.close();\r
228         }\r
229 \r
230         public static void writeFastaKeepTheStream(final OutputStream outstream,\r
231                         final List<FastaSequence> sequences, final int width)\r
232                         throws IOException {\r
233                 final OutputStreamWriter writer = new OutputStreamWriter(outstream);\r
234                 final BufferedWriter fastawriter = new BufferedWriter(writer);\r
235                 for (final FastaSequence fs : sequences) {\r
236                         fastawriter.write(">" + fs.getId() + "\n");\r
237                         fastawriter.write(fs.getFormatedSequence(width));\r
238                         fastawriter.write("\n");\r
239                 }\r
240                 fastawriter.flush();\r
241                 writer.flush();\r
242         }\r
243 \r
244         /**\r
245          * Reads fasta sequences from inStream into the list of FastaSequence\r
246          * objects\r
247          * \r
248          * @param inStream\r
249          *            from\r
250          * @return list of FastaSequence objects\r
251          * @throws IOException\r
252          */\r
253         public static List<FastaSequence> readFasta(final InputStream inStream)\r
254                         throws IOException {\r
255                 final List<FastaSequence> seqs = new ArrayList<FastaSequence>();\r
256 \r
257                 final BufferedReader infasta = new BufferedReader(\r
258                                 new InputStreamReader(inStream, "UTF8"), 16000);\r
259                 final Pattern pattern = Pattern.compile("//s+");\r
260 \r
261                 String line;\r
262                 String sname = "", seqstr = null;\r
263                 do {\r
264                         line = infasta.readLine();\r
265                         if ((line == null) || line.startsWith(">")) {\r
266                                 if (seqstr != null) {\r
267                                         seqs.add(new FastaSequence(sname.substring(1), seqstr));\r
268                                 }\r
269                                 sname = line; // remove >\r
270                                 seqstr = "";\r
271                         } else {\r
272                                 final String subseq = pattern.matcher(line).replaceAll("");\r
273                                 seqstr += subseq;\r
274                         }\r
275                 } while (line != null);\r
276 \r
277                 infasta.close();\r
278                 return seqs;\r
279         }\r
280 \r
281         /**\r
282          * Writes FastaSequence in the file, each sequence will take one line only\r
283          * \r
284          * @param os\r
285          * @param sequences\r
286          * @throws IOException\r
287          */\r
288         public static void writeFasta(final OutputStream os,\r
289                         final List<FastaSequence> sequences) throws IOException {\r
290                 final OutputStreamWriter outWriter = new OutputStreamWriter(os);\r
291                 final BufferedWriter fasta_out = new BufferedWriter(outWriter);\r
292                 for (final FastaSequence fs : sequences) {\r
293                         fasta_out.write(fs.getOnelineFasta());\r
294                 }\r
295                 fasta_out.close();\r
296                 outWriter.close();\r
297         }\r
298 \r
299         public static List<AnnotatedSequence> readJRonn(final File result)\r
300                         throws IOException, UnknownFileFormatException {\r
301                 InputStream input = new FileInputStream(result);\r
302                 List<AnnotatedSequence> sequences = readJRonn(input);\r
303                 input.close();\r
304                 return sequences;\r
305         }\r
306 \r
307         /**\r
308          * Reader for JRonn horizontal file format >Foobar M G D T T A G 0.48 0.42\r
309          * 0.42 0.48 0.52 0.53 0.54 All values are tab delimited\r
310          * \r
311          * @param inStream\r
312          * @return\r
313          * @throws IOException\r
314          * @throws UnknownFileFormatException\r
315          */\r
316         public static List<AnnotatedSequence> readJRonn(final InputStream inStream)\r
317                         throws IOException, UnknownFileFormatException {\r
318                 final List<AnnotatedSequence> seqs = new ArrayList<AnnotatedSequence>();\r
319 \r
320                 final BufferedReader infasta = new BufferedReader(\r
321                                 new InputStreamReader(inStream, "UTF8"), 16000);\r
322 \r
323                 String line;\r
324                 String sname = "";\r
325                 do {\r
326                         line = infasta.readLine();\r
327                         if (line == null || line.isEmpty()) {\r
328                                 // skip empty lines\r
329                                 continue;\r
330                         }\r
331                         if (line.startsWith(">")) {\r
332                                 // read name\r
333                                 sname = line.trim().substring(1);\r
334                                 // read sequence line\r
335                                 line = infasta.readLine();\r
336                                 final String sequence = line.replace("\t", "");\r
337                                 // read annotation line\r
338                                 line = infasta.readLine();\r
339                                 String[] annotValues = line.split("\t");\r
340                                 float[] annotation = convertToNumber(annotValues);\r
341                                 if (annotation.length != sequence.length()) {\r
342                                         throw new UnknownFileFormatException(\r
343                                                         "File does not look like Jronn horizontally formatted output file!\n"\r
344                                                                         + JRONN_WRONG_FORMAT_MESSAGE);\r
345                                 }\r
346                                 seqs.add(new AnnotatedSequence(sname, sequence, annotation));\r
347                         }\r
348                 } while (line != null);\r
349 \r
350                 infasta.close();\r
351                 return seqs;\r
352         }\r
353 \r
354         private static float[] convertToNumber(String[] annotValues)\r
355                         throws UnknownFileFormatException {\r
356                 float[] annotation = new float[annotValues.length];\r
357                 try {\r
358                         for (int i = 0; i < annotation.length; i++) {\r
359                                 annotation[i] = Float.parseFloat(annotValues[i]);\r
360                         }\r
361                 } catch (NumberFormatException e) {\r
362                         throw new UnknownFileFormatException(JRONN_WRONG_FORMAT_MESSAGE,\r
363                                         e.getCause());\r
364                 }\r
365                 return annotation;\r
366         }\r
367 \r
368         private static final String JRONN_WRONG_FORMAT_MESSAGE = "Jronn file must be in the following format:\n"\r
369                         + ">sequence_name\n "\r
370                         + "M    V       S\n"\r
371                         + "0.43 0.22    0.65\n"\r
372                         + "Where first line is the sequence name,\n"\r
373                         + "second line is the tab delimited sequence,\n"\r
374                         + "third line contains tab delimited disorder prediction values.\n"\r
375                         + "No lines are allowed between these three. Additionally, the number of  "\r
376                         + "sequence residues must be equal to the number of the disorder values.";\r
377 \r
378         /**\r
379          * Closes the Closable and logs the exception if any\r
380          * \r
381          * @param log\r
382          * @param stream\r
383          */\r
384         public final static void closeSilently(java.util.logging.Logger log,\r
385                         Closeable stream) {\r
386                 if (stream != null) {\r
387                         try {\r
388                                 stream.close();\r
389                         } catch (IOException e) {\r
390                                 log.log(Level.WARNING, e.getLocalizedMessage(), e.getCause());\r
391                         }\r
392                 }\r
393         }\r
394 \r
395         /**\r
396          * \r
397          * TODO complete!\r
398          * \r
399          * # RESIDUE COILS REM465 HOTLOOPS M 0.86010 0.88512 0.37094 T 0.79983\r
400          * 0.85864 0.44331 .... # RESIDUE COILS REM465 HOTLOOPS M 0.86010 0.88512\r
401          * 0.37094\r
402          * \r
403          * @param input\r
404          * @return\r
405          * @throws IOException\r
406          * @throws UnknownFileFormatException\r
407          */\r
408         public static List<MultiAnnotatedSequence<DisemblResultAnnot>> readDisembl(\r
409                         final InputStream input) throws IOException,\r
410                         UnknownFileFormatException {\r
411                 Scanner scan = new Scanner(input);\r
412                 scan.useDelimiter("# RESIDUE COILS REM465 HOTLOOPS\n");\r
413                 if (!scan.hasNext()) {\r
414                         throw new UnknownFileFormatException(\r
415                                         "In Disembl score format each seqeunce score is expected to start from the line: "\r
416                                                         + "'# RESIDUE COILS REM465 HOTLOOPS\\n'."\r
417                                                         + " No such line was found!");\r
418                 }\r
419 \r
420                 List<MultiAnnotatedSequence<DisemblResultAnnot>> results = new ArrayList<MultiAnnotatedSequence<DisemblResultAnnot>>();\r
421                 int seqCounter = 0;\r
422                 while (scan.hasNext()) {\r
423                         seqCounter++;\r
424                         String singleSeq = scan.next();\r
425                         Scanner scansingle = new Scanner(singleSeq);\r
426                         StringBuffer seqbuffer = new StringBuffer();\r
427                         ArrayList<Float> coils = new ArrayList<Float>();\r
428                         ArrayList<Float> rem = new ArrayList<Float>();\r
429                         ArrayList<Float> hotloops = new ArrayList<Float>();\r
430 \r
431                         MultiAnnotatedSequence<DisemblResultAnnot> disemblRes = new MultiAnnotatedSequence<DisemblResultAnnot>(\r
432                                         DisemblResultAnnot.class);\r
433 \r
434                         while (scansingle.hasNextLine()) {\r
435                                 String valueLine = scansingle.nextLine();\r
436                                 Scanner values = new Scanner(valueLine);\r
437                                 seqbuffer.append(values.next());\r
438                                 coils.add(values.nextFloat());\r
439                                 rem.add(values.nextFloat());\r
440                                 hotloops.add(values.nextFloat());\r
441                                 values.close();\r
442                         }\r
443                         disemblRes.addAnnotation(DisemblResultAnnot.COILS, coils);\r
444                         disemblRes.addAnnotation(DisemblResultAnnot.REM465, rem);\r
445                         disemblRes.addAnnotation(DisemblResultAnnot.HOTLOOPS, hotloops);\r
446                         // TODO\r
447                         // disemblRes.sequence = seqbuffer.toString();\r
448                         scansingle.close();\r
449                         results.add(disemblRes);\r
450                 }\r
451 \r
452                 input.close();\r
453                 return results;\r
454         }\r
455 \r
456         /**\r
457          * Read AACon result with no alignment files. This method leaves incoming\r
458          * the InputStream results open!\r
459          * \r
460          * @param results\r
461          *            output file of AAConservation\r
462          * @return Map with keys {@link Method} -> float[]\r
463          */\r
464         public static HashSet<Score> readAAConResults(InputStream results) {\r
465                 if (results == null) {\r
466                         throw new NullPointerException(\r
467                                         "InputStream with results must be provided");\r
468                 }\r
469                 HashSet<Score> annotations = new HashSet<Score>();\r
470                 Scanner sc = new Scanner(results);\r
471                 sc.useDelimiter("#");\r
472                 while (sc.hasNext()) {\r
473                         String line = sc.next();\r
474                         int spacePos = line.indexOf(" ");\r
475                         assert spacePos > 0 : "Space is expected as delimited between method "\r
476                                         + "name and values!";\r
477                         String methodLine = line.substring(0, spacePos);\r
478                         Method method = Method.getMethod(methodLine);\r
479                         assert method != null : "Method " + methodLine\r
480                                         + " is not recognized! ";\r
481                         Scanner valuesScanner = new Scanner(line.substring(spacePos));\r
482                         ArrayList<Float> values = new ArrayList<Float>();\r
483                         while (valuesScanner.hasNextDouble()) {\r
484                                 Double value = valuesScanner.nextDouble();\r
485                                 values.add(value.floatValue());\r
486                         }\r
487                         annotations.add(new Score(method, values));\r
488                 }\r
489                 return annotations;\r
490         }\r
491 \r
492 }\r