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