f8f2cdeed5d78addce1c876b26c967e27991eb75
[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.HMMFile;
16 import jalview.io.StockholmFile;
17 import jalview.util.FileUtils;
18 import jalview.util.MessageManager;
19 import jalview.util.Platform;
20 import jalview.ws.params.ArgumentI;
21
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.PrintWriter;
27 import java.util.ArrayList;
28 import java.util.Hashtable;
29 import java.util.List;
30
31 /**
32  * Base class for hmmbuild, hmmalign and hmmsearch
33  * 
34  * @author TZVanaalten
35  *
36  */
37 public abstract class HmmerCommand implements Runnable
38 {
39   public static final String HMMBUILD = "hmmbuild";
40
41   protected final AlignFrame af;
42
43   protected final AlignmentI alignment;
44
45   protected final List<ArgumentI> params;
46
47   /**
48    * Constructor
49    * 
50    * @param alignFrame
51    * @param args
52    */
53   public HmmerCommand(AlignFrame alignFrame, List<ArgumentI> args)
54   {
55     af = alignFrame;
56     alignment = af.getViewport().getAlignment();
57     params = args;
58   }
59
60   /**
61    * Answers true if preference HMMER_PATH is set, and its value is the path to
62    * a directory that contains an executable <code>hmmbuild</code> or
63    * <code>hmmbuild.exe</code>, else false
64    * 
65    * @return
66    */
67   public static boolean isHmmerAvailable()
68   {
69     File exec = FileUtils.getExecutable(HMMBUILD,
70             Cache.getProperty(Preferences.HMMER_PATH));
71     return exec != null;
72   }
73
74   /**
75    * Uniquifies the sequences when exporting and stores their details in a
76    * hashtable
77    * 
78    * @param seqs
79    */
80   protected Hashtable stashSequences(SequenceI[] seqs)
81   {
82     return SeqsetUtils.uniquify(seqs, true);
83   }
84
85   /**
86    * Restores the sequence data lost by uniquifying
87    * 
88    * @param hashtable
89    * @param seqs
90    */
91   protected void recoverSequences(Hashtable hashtable, SequenceI[] seqs)
92   {
93     SeqsetUtils.deuniquify(hashtable, seqs);
94   }
95
96   /**
97    * Runs a command as a separate process and waits for it to complete. Answers
98    * true if the process return status is zero, else false.
99    * 
100    * @param commands
101    *          the executable command and any arguments to it
102    * @throws IOException
103    */
104   public boolean runCommand(List<String> commands)
105           throws IOException
106   {
107     List<String> args = Platform.isWindows() ? wrapWithCygwin(commands)
108             : commands;
109
110     try
111     {
112       ProcessBuilder pb = new ProcessBuilder(args);
113       pb.redirectErrorStream(true); // merge syserr to sysout
114       final Process p = pb.start();
115       new Thread(new Runnable()
116       {
117         @Override
118         public void run()
119         {
120           BufferedReader input = new BufferedReader(
121                   new InputStreamReader(p.getInputStream()));
122           try
123           {
124             String line = input.readLine();
125             while (line != null)
126             {
127               System.out.println(line);
128               line = input.readLine();
129             }
130           } catch (IOException e)
131           {
132             e.printStackTrace();
133           }
134         }
135       }).start();
136
137       p.waitFor();
138       int exitValue = p.exitValue();
139       if (exitValue != 0)
140       {
141         Cache.log.error("Command failed, return code = " + exitValue);
142         Cache.log.error("Command/args were: " + args.toString());
143       }
144       return exitValue == 0; // 0 is success, by convention
145     } catch (Exception e)
146     {
147       e.printStackTrace();
148       return false;
149     }
150   }
151
152   /**
153    * Converts the given command to a Cygwin "bash" command wrapper. The hmmer
154    * command and any arguments to it are converted into a single parameter to the
155    * bash command.
156    * 
157    * @param commands
158    */
159   protected List<String> wrapWithCygwin(List<String> commands)
160   {
161     File bash = FileUtils.getExecutable("bash",
162             Cache.getProperty(Preferences.CYGWIN_PATH));
163     if (bash == null)
164     {
165       Cache.log.error("Cygwin shell not found");
166       return commands;
167     }
168
169     List<String> wrapped = new ArrayList<>();
170     wrapped.add(bash.getAbsolutePath());
171     wrapped.add("-c");
172
173     /*
174      * combine hmmbuild/search/align and arguments to a single string
175      */
176     StringBuilder sb = new StringBuilder();
177     for (String cmd : commands)
178     {
179       sb.append(" ").append(cmd);
180     }
181     wrapped.add(sb.toString());
182
183     return wrapped;
184   }
185
186   /**
187    * Exports an alignment, and reference (RF) annotation if present, to the
188    * specified file, in Stockholm format
189    * 
190    * @param seqs
191    * @param toFile
192    * @param annotated
193    * @throws IOException
194    */
195   public void exportStockholm(SequenceI[] seqs, File toFile,
196           AnnotatedCollectionI annotated) throws IOException
197   {
198     if (seqs == null)
199     {
200       return;
201     }
202     AlignmentI newAl = new Alignment(seqs);
203     if (!newAl.isAligned())
204     {
205       newAl.padGaps();
206     }
207
208     if (toFile != null && annotated != null)
209     {
210       AlignmentAnnotation[] annots = annotated.getAlignmentAnnotation();
211       if (annots != null)
212       {
213         for (AlignmentAnnotation annot : annots)
214         {
215           if (annot.label.contains("Reference") || "RF".equals(annot.label))
216           {
217             AlignmentAnnotation newRF;
218             if (annot.annotations.length > newAl.getWidth())
219             {
220               Annotation[] rfAnnots = new Annotation[newAl.getWidth()];
221               System.arraycopy(annot.annotations, 0, rfAnnots, 0,
222                       rfAnnots.length);
223               newRF = new AlignmentAnnotation("RF", "Reference Positions",
224                       rfAnnots);
225             }
226             else
227             {
228               newRF = new AlignmentAnnotation(annot);
229             }
230             newAl.addAnnotation(newRF);
231           }
232         }
233       }
234     }
235
236     StockholmFile file = new StockholmFile(newAl);
237     String output = file.print(seqs, false);
238     PrintWriter writer = new PrintWriter(toFile);
239     writer.println(output);
240     writer.close();
241   }
242
243   /**
244    * Answers the full path to the given hmmer executable, or null if file cannot
245    * be found or is not executable
246    * 
247    * @param cmd
248    *          command short name e.g. hmmalign
249    * @return
250    */
251   protected String getCommandPath(String cmd)
252   {
253     String binariesFolder = Cache.getProperty(Preferences.HMMER_PATH);
254     File file = FileUtils.getExecutable(cmd, binariesFolder);
255     if (file == null && af != null)
256     {
257       JvOptionPane.showInternalMessageDialog(af, MessageManager
258               .formatMessage("label.executable_not_found", cmd));
259     }
260
261     return file == null ? null : getFilePath(file);
262   }
263
264   /**
265    * Exports an HMM to the specified file
266    * 
267    * @param hmm
268    * @param hmmFile
269    * @throws IOException
270    */
271   public void exportHmm(HiddenMarkovModel hmm, File hmmFile)
272           throws IOException
273   {
274     if (hmm != null)
275     {
276       HMMFile file = new HMMFile(hmm);
277       PrintWriter writer = new PrintWriter(hmmFile);
278       writer.print(file.print());
279       writer.close();
280     }
281   }
282
283   /**
284    * Answers the HMM profile for the profile sequence the user selected (default
285    * is just the first HMM sequence in the alignment)
286    * 
287    * @return
288    */
289   protected HiddenMarkovModel getHmmProfile()
290   {
291     String alignToParamName = MessageManager.getString("label.use_hmm");
292     for (ArgumentI arg : params)
293     {
294       String name = arg.getName();
295       if (name.equals(alignToParamName))
296       {
297         String seqName = arg.getValue();
298         SequenceI hmmSeq = alignment.findName(seqName);
299         if (hmmSeq.hasHMMProfile())
300         {
301           return hmmSeq.getHMM();
302         }
303       }
304     }
305     return null;
306   }
307
308   /**
309    * Answers an absolute path to the given file, in a format suitable for
310    * processing by a hmmer command. On a Windows platform, the native Windows file
311    * path is converted to Cygwin format, by replacing '\'with '/' and drive letter
312    * X with /cygdrive/x.
313    * 
314    * @param resultFile
315    * @return
316    */
317   protected String getFilePath(File resultFile)
318   {
319     String path = resultFile.getAbsolutePath();
320     if (Platform.isWindows())
321     {
322       // the first backslash escapes '\' for the regular expression argument
323       path = path.replaceAll("\\" + File.separator, "/");
324       int colon = path.indexOf(':');
325       if (colon > 0)
326       {
327         String drive = path.substring(0, colon);
328         path = path.replaceAll(drive + ":", "/cygdrive/" + drive);
329       }
330     }
331
332     return path;
333   }
334 }