JAL-2937 Cygwin path preference, method refactoring
[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 command
101    *          the executable command and any arguments to it
102    * @throws IOException
103    */
104   public boolean runCommand(List<String> command)
105           throws IOException
106   {
107     List<String> commands = Platform.isWindows() ? wrapWithCygwin(command)
108             : command;
109
110     try
111     {
112       ProcessBuilder pb = new ProcessBuilder(commands);
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: " + commands.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 "run" command wrapper
154    * 
155    * @param command
156    * @return
157    */
158   protected List<String> wrapWithCygwin(List<String> command)
159   {
160     File runCygwin = FileUtils.getExecutable("run",
161             Cache.getProperty(Preferences.CYGWIN_PATH));
162     if (runCygwin == null)
163     {
164       Cache.log.error("Cygwin shell not found");
165       return command;
166     }
167
168     List<String> wrapped = new ArrayList<>();
169     wrapped.add(runCygwin.getAbsolutePath());
170     if (!command.isEmpty())
171     {
172       wrapped.add(command.get(0));
173       // wrapped.add("--quote");
174       StringBuilder args = new StringBuilder();
175       for (String arg : command.subList(1, command.size()))
176       {
177         args.append(" ").append(arg);
178       }
179       wrapped.add(args.toString());
180     }
181     // TODO this doesn't yet pass parameters successfully
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,
258                 MessageManager.getString("warn.hmm_command_failed"));
259     }
260
261     return file == null ? null : file.getAbsolutePath();
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 }