JAL-2629 add simple jackhmmer functionality
[jalview.git] / src / jalview / hmmer / HmmerCommand.java
1 package jalview.hmmer;
2
3 import jalview.analysis.SeqsetUtils;
4 import jalview.bin.Cache;
5 import jalview.datamodel.Alignment;
6 import jalview.datamodel.AlignmentAnnotation;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.AnnotatedCollectionI;
9 import jalview.datamodel.Annotation;
10 import jalview.datamodel.HiddenMarkovModel;
11 import jalview.datamodel.SequenceI;
12 import jalview.gui.AlignFrame;
13 import jalview.gui.JvOptionPane;
14 import jalview.gui.Preferences;
15 import jalview.io.FastaFile;
16 import jalview.io.HMMFile;
17 import jalview.io.StockholmFile;
18 import jalview.util.FileUtils;
19 import jalview.util.MessageManager;
20 import jalview.util.Platform;
21 import jalview.ws.params.ArgumentI;
22
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.io.PrintWriter;
29 import java.nio.file.Paths;
30 import java.util.ArrayList;
31 import java.util.Hashtable;
32 import java.util.List;
33
34 /**
35  * Base class for hmmbuild, hmmalign and hmmsearch
36  * 
37  * @author TZVanaalten
38  *
39  */
40 public abstract class HmmerCommand implements Runnable
41 {
42   public static final String HMMBUILD = "hmmbuild";
43
44   protected final AlignFrame af;
45
46   protected final AlignmentI alignment;
47
48   protected final List<ArgumentI> params;
49
50   /*
51    * constants for i18n lookup of passed parameter names
52    */
53   static final String DATABASE_KEY = "label.database";
54
55   static final String THIS_ALIGNMENT_KEY = "label.this_alignment";
56
57   static final String USE_ACCESSIONS_KEY = "label.use_accessions";
58
59   static final String AUTO_ALIGN_SEQS_KEY = "label.auto_align_seqs";
60
61   static final String NUMBER_OF_RESULTS_KEY = "label.number_of_results";
62
63   static final String TRIM_TERMINI_KEY = "label.trim_termini";
64
65   static final String REPORTING_CUTOFF_KEY = "label.reporting_cutoff";
66
67   static final String CUTOFF_NONE = "None";
68
69   static final String CUTOFF_SCORE = "Score";
70
71   static final String CUTOFF_EVALUE = "E-Value";
72
73   static final String SEQ_EVALUE_KEY = "label.seq_evalue";
74
75   static final String DOM_EVALUE_KEY = "label.dom_evalue";
76
77   static final String SEQ_SCORE_KEY = "label.seq_score";
78
79   static final String DOM_SCORE_KEY = "label.dom_score";
80
81   static final String ARG_TRIM = "--trim";
82
83   /**
84    * Constructor
85    * 
86    * @param alignFrame
87    * @param args
88    */
89   public HmmerCommand(AlignFrame alignFrame, List<ArgumentI> args)
90   {
91     af = alignFrame;
92     alignment = af.getViewport().getAlignment();
93     params = args;
94   }
95
96   /**
97    * Answers true if preference HMMER_PATH is set, and its value is the path to
98    * a directory that contains an executable <code>hmmbuild</code> or
99    * <code>hmmbuild.exe</code>, else false
100    * 
101    * @return
102    */
103   public static boolean isHmmerAvailable()
104   {
105     File exec = FileUtils.getExecutable(HMMBUILD,
106             Cache.getProperty(Preferences.HMMER_PATH));
107     return exec != null;
108   }
109
110   /**
111    * Uniquifies the sequences when exporting and stores their details in a
112    * hashtable
113    * 
114    * @param seqs
115    */
116   protected Hashtable stashSequences(SequenceI[] seqs)
117   {
118     return SeqsetUtils.uniquify(seqs, true);
119   }
120
121   /**
122    * Restores the sequence data lost by uniquifying
123    * 
124    * @param hashtable
125    * @param seqs
126    */
127   protected void recoverSequences(Hashtable hashtable, SequenceI[] seqs)
128   {
129     SeqsetUtils.deuniquify(hashtable, seqs);
130   }
131
132   /**
133    * Runs a command as a separate process and waits for it to complete. Answers
134    * true if the process return status is zero, else false.
135    * 
136    * @param commands
137    *          the executable command and any arguments to it
138    * @throws IOException
139    */
140   public boolean runCommand(List<String> commands)
141           throws IOException
142   {
143     List<String> args = Platform.isWindows() ? wrapWithCygwin(commands)
144             : commands;
145
146     try
147     {
148       ProcessBuilder pb = new ProcessBuilder(args);
149       pb.redirectErrorStream(true); // merge syserr to sysout
150       if (Platform.isWindows())
151       {
152         String path = pb.environment().get("Path");
153         path = jalview.bin.Cache.getProperty("CYGWIN_PATH") + ";" + path;
154         pb.environment().put("Path", path);
155       }
156       final Process p = pb.start();
157       new Thread(new Runnable()
158       {
159         @Override
160         public void run()
161         {
162           BufferedReader input = new BufferedReader(
163                   new InputStreamReader(p.getInputStream()));
164           try
165           {
166             String line = input.readLine();
167             while (line != null)
168             {
169               System.out.println(line);
170               line = input.readLine();
171             }
172           } catch (IOException e)
173           {
174             e.printStackTrace();
175           }
176         }
177       }).start();
178
179       p.waitFor();
180       int exitValue = p.exitValue();
181       if (exitValue != 0)
182       {
183         Cache.log.error("Command failed, return code = " + exitValue);
184         Cache.log.error("Command/args were: " + args.toString());
185       }
186       return exitValue == 0; // 0 is success, by convention
187     } catch (Exception e)
188     {
189       e.printStackTrace();
190       return false;
191     }
192   }
193
194   /**
195    * Converts the given command to a Cygwin "bash" command wrapper. The hmmer
196    * command and any arguments to it are converted into a single parameter to the
197    * bash command.
198    * 
199    * @param commands
200    */
201   protected List<String> wrapWithCygwin(List<String> commands)
202   {
203     File bash = FileUtils.getExecutable("bash",
204             Cache.getProperty(Preferences.CYGWIN_PATH));
205     if (bash == null)
206     {
207       Cache.log.error("Cygwin shell not found");
208       return commands;
209     }
210
211     List<String> wrapped = new ArrayList<>();
212     // wrapped.add("C:\Users\tva\run");
213     wrapped.add(bash.getAbsolutePath());
214     wrapped.add("-c");
215
216     /*
217      * combine hmmbuild/search/align and arguments to a single string
218      */
219     StringBuilder sb = new StringBuilder();
220     for (String cmd : commands)
221     {
222       sb.append(" ").append(cmd);
223     }
224     wrapped.add(sb.toString());
225
226     return wrapped;
227   }
228
229   /**
230    * Exports an alignment, and reference (RF) annotation if present, to the
231    * specified file, in Stockholm format
232    * 
233    * @param seqs
234    * @param toFile
235    * @param annotated
236    * @throws IOException
237    */
238   public void exportStockholm(SequenceI[] seqs, File toFile,
239           AnnotatedCollectionI annotated) throws IOException
240   {
241     if (seqs == null)
242     {
243       return;
244     }
245     AlignmentI newAl = new Alignment(seqs);
246     if (!newAl.isAligned())
247     {
248       newAl.padGaps();
249     }
250
251     if (toFile != null && annotated != null)
252     {
253       AlignmentAnnotation[] annots = annotated.getAlignmentAnnotation();
254       if (annots != null)
255       {
256         for (AlignmentAnnotation annot : annots)
257         {
258           if (annot.label.contains("Reference") || "RF".equals(annot.label))
259           {
260             AlignmentAnnotation newRF;
261             if (annot.annotations.length > newAl.getWidth())
262             {
263               Annotation[] rfAnnots = new Annotation[newAl.getWidth()];
264               System.arraycopy(annot.annotations, 0, rfAnnots, 0,
265                       rfAnnots.length);
266               newRF = new AlignmentAnnotation("RF", "Reference Positions",
267                       rfAnnots);
268             }
269             else
270             {
271               newRF = new AlignmentAnnotation(annot);
272             }
273             newAl.addAnnotation(newRF);
274           }
275         }
276       }
277     }
278
279     StockholmFile file = new StockholmFile(newAl);
280     String output = file.print(seqs, false);
281     PrintWriter writer = new PrintWriter(toFile);
282     writer.println(output);
283     writer.close();
284   }
285
286   public void exportFasta(SequenceI[] seqs, File toFile)
287   {
288     FastaFile file = new FastaFile();
289     String output = file.print(seqs, false);
290     PrintWriter writer;
291     try
292     {
293       writer = new PrintWriter(toFile);
294       writer.println(output);
295       writer.close();
296     } catch (FileNotFoundException e)
297     {
298       e.printStackTrace();
299     }
300
301   }
302
303   /**
304    * Answers the full path to the given hmmer executable, or null if file cannot
305    * be found or is not executable
306    * 
307    * @param cmd
308    *          command short name e.g. hmmalign
309    * @return
310    * @throws IOException
311    */
312   protected String getCommandPath(String cmd)
313           throws IOException
314   {
315     String binariesFolder = Cache.getProperty(Preferences.HMMER_PATH);
316     // ensure any symlink to the directory is resolved:
317     binariesFolder = Paths.get(binariesFolder).toRealPath().toString();
318     File file = FileUtils.getExecutable(cmd, binariesFolder);
319     if (file == null && af != null)
320     {
321       JvOptionPane.showInternalMessageDialog(af, MessageManager
322               .formatMessage("label.executable_not_found", cmd));
323     }
324
325     return file == null ? null : getFilePath(file, true);
326   }
327
328   /**
329    * Exports an HMM to the specified file
330    * 
331    * @param hmm
332    * @param hmmFile
333    * @throws IOException
334    */
335   public void exportHmm(HiddenMarkovModel hmm, File hmmFile)
336           throws IOException
337   {
338     if (hmm != null)
339     {
340       HMMFile file = new HMMFile(hmm);
341       PrintWriter writer = new PrintWriter(hmmFile);
342       writer.print(file.print());
343       writer.close();
344     }
345   }
346
347   // TODO is needed?
348   /**
349    * Exports a sequence to the specified file
350    * 
351    * @param hmm
352    * @param hmmFile
353    * @throws IOException
354    */
355   public void exportSequence(SequenceI seq, File seqFile) throws IOException
356   {
357     if (seq != null)
358     {
359       FastaFile file = new FastaFile();
360       PrintWriter writer = new PrintWriter(seqFile);
361       writer.print(file.print(new SequenceI[] { seq }, false));
362       writer.close();
363     }
364   }
365
366   /**
367    * Answers the HMM profile for the profile sequence the user selected (default
368    * is just the first HMM sequence in the alignment)
369    * 
370    * @return
371    */
372   protected HiddenMarkovModel getHmmProfile()
373   {
374     String alignToParamName = MessageManager.getString("label.use_hmm");
375     for (ArgumentI arg : params)
376     {
377       String name = arg.getName();
378       if (name.equals(alignToParamName))
379       {
380         String seqName = arg.getValue();
381         SequenceI hmmSeq = alignment.findName(seqName);
382         if (hmmSeq.hasHMMProfile())
383         {
384           return hmmSeq.getHMM();
385         }
386       }
387     }
388     return null;
389   }
390
391   /**
392    * Answers the HMM profile for the profile sequence the user selected (default
393    * is just the first HMM sequence in the alignment)
394    * 
395    * @return
396    */
397   protected SequenceI getSequence()
398   {
399     String alignToParamName = MessageManager
400             .getString("label.use_sequence");
401     for (ArgumentI arg : params)
402     {
403       String name = arg.getName();
404       if (name.equals(alignToParamName))
405       {
406         String seqName = arg.getValue();
407         SequenceI seq = alignment.findName(seqName);
408         return seq;
409       }
410     }
411     return null;
412   }
413
414   /**
415    * Answers an absolute path to the given file, in a format suitable for
416    * processing by a hmmer command. On a Windows platform, the native Windows file
417    * path is converted to Cygwin format, by replacing '\'with '/' and drive letter
418    * X with /cygdrive/x.
419    * 
420    * @param resultFile
421    * @param isInCygwin
422    *                     True if file is to be read/written from within the Cygwin
423    *                     shell. Should be false for any imports.
424    * @return
425    */
426   protected String getFilePath(File resultFile, boolean isInCygwin)
427   {
428     String path = resultFile.getAbsolutePath();
429     if (Platform.isWindows() && isInCygwin)
430     {
431       // the first backslash escapes '\' for the regular expression argument
432       path = path.replaceAll("\\" + File.separator, "/");
433       int colon = path.indexOf(':');
434       if (colon > 0)
435       {
436         String drive = path.substring(0, colon);
437         path = path.replaceAll(drive + ":", "/cygdrive/" + drive);
438       }
439     }
440
441     return path;
442   }
443 }