JAL-2629 partial refactoring of Hmmer command classes
[jalview.git] / src / jalview / hmmer / HMMAlignThread.java
1 package jalview.hmmer;
2
3 import jalview.analysis.AlignmentSorter;
4 import jalview.analysis.SeqsetUtils;
5 import jalview.bin.Cache;
6 import jalview.datamodel.Alignment;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.AlignmentOrder;
9 import jalview.datamodel.AlignmentView;
10 import jalview.datamodel.HiddenColumns;
11 import jalview.datamodel.HiddenMarkovModel;
12 import jalview.datamodel.SequenceI;
13 import jalview.gui.AlignFrame;
14 import jalview.gui.Desktop;
15 import jalview.gui.JvOptionPane;
16 import jalview.gui.SplitFrame;
17 import jalview.io.DataSourceType;
18 import jalview.io.StockholmFile;
19 import jalview.util.MessageManager;
20 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
21 import jalview.ws.params.ArgumentI;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import javax.swing.JInternalFrame;
29
30 public class HMMAlignThread extends HmmerCommand implements Runnable
31 {
32   static final String HMMALIGN = "hmmalign";
33
34   /*
35    * feature settings from view that job was associated with
36    */
37   protected FeatureRendererSettings featureSettings = null;
38
39   AlignmentI alignment;
40
41   AlignmentI dataset;
42
43   List<AlignmentOrder> orders;
44
45   AlignmentView msa;
46
47   HiddenMarkovModel hmm;
48
49   List<ArgumentI> args;
50
51   boolean newFrame;
52
53   long barID;
54
55   File hmmTemp = null;
56
57   File outTemp = null;
58
59   File inputTemp = null;
60
61   List<AlignmentOrder> allOrders;
62
63   SequenceI[][] allResults;
64
65   /**
66    * Constructor for the HMMAlignThread. If create new frame is set to true, a
67    * new frame will be created.
68    * 
69    * @param af
70    * @param createNewFrame
71    */
72   public HMMAlignThread(AlignFrame af, boolean createNewFrame,
73           List<ArgumentI> args)
74   {
75     this.af = af;
76     alignment = af.getViewport().getAlignment();
77     if (alignment.getDataset() != null)
78     {
79       dataset = alignment.getDataset();
80     }
81     newFrame = createNewFrame;
82     featureSettings = af.getFeatureRenderer().getSettings();
83     this.args = args;
84   }
85
86   /**
87    * Runs the HMMAlignThread: the data on the alignment or group is exported,
88    * then the command is executed in the command line and then the data is
89    * imported and displayed in a new frame (if true). The command is executed
90    * for each segemtn of the alignment.
91    */
92   @Override
93   public void run()
94   {
95
96     hmm = af.getSelectedHMM();
97
98     barID = System.currentTimeMillis();
99     af.setProgressBar(MessageManager.getString("status.running_hmmalign"),
100             barID);
101     prepareAlignment();
102     SequenceI[][] subAlignments = msa.getVisibleContigs('-');
103     allOrders = new ArrayList<>();
104     allResults = new SequenceI[subAlignments.length][];
105     int job = 0;
106     for (SequenceI[] seqs : subAlignments)
107     {
108       uniquifySequences(seqs);
109       try
110       {
111         createTemporaryFiles();
112       } catch (IOException e2)
113       {
114         e2.printStackTrace();
115       }
116       try
117       {
118         exportData(seqs, outTemp.getAbsoluteFile(), hmm,
119                 hmmTemp.getAbsoluteFile(), null);
120       } catch (IOException e1)
121       {
122         e1.printStackTrace();
123       }
124       try
125       {
126         boolean ran = runCommand();
127         if (!ran)
128         {
129           JvOptionPane.showInternalMessageDialog(af,
130                   MessageManager.getString("warn.hmmalign_failed"));
131           return;
132         }
133       } catch (IOException | InterruptedException e)
134       {
135         e.printStackTrace();
136       }
137       try
138       {
139         importData(job);
140       } catch (IOException | InterruptedException e)
141       {
142         // TODO Auto-generated catch block
143         e.printStackTrace();
144       }
145       job++;
146     }
147
148     displayResults(newFrame);
149
150     af.setProgressBar("", barID);
151
152   }
153
154   /**
155    * Creates temporary files for exporting and importing the data.
156    * 
157    * @throws IOException
158    */
159   private void createTemporaryFiles() throws IOException
160   {
161     if (hmmTemp == null)
162     {
163       hmmTemp = File.createTempFile("hmm", ".hmm");
164       hmmTemp.deleteOnExit();
165     }
166     if (outTemp == null)
167     {
168       outTemp = File.createTempFile("output", ".sto");
169       outTemp.deleteOnExit();
170     }
171     inputTemp = File.createTempFile("input", ".sto");
172     inputTemp.deleteOnExit();
173   }
174
175   /**
176    * Executes the hmmalign command in the command line
177    * 
178    * @return
179    * @throws IOException
180    * @throws InterruptedException
181    */
182   private boolean runCommand() throws IOException, InterruptedException
183   {
184     String binaryPath = getCommandRoot(HMMALIGN);
185     if (binaryPath == null) {
186       return false;
187     }
188     String command = binaryPath + SPACE;
189     // todo parse version from ./hmmerbuild -h
190     String version = Cache.getProperty("HMMER_VERSION");
191     if (!"3.1b2".equals(version))
192     {
193       // command += ALLCOL; // todo obsolete option?
194     }
195     if (args != null)
196     {
197       for (ArgumentI arg : args)
198       {
199         String name = arg.getName();
200         switch (name)
201         {
202         case "Trim Non-Matching Termini":
203           command += "--trim";
204         }
205       }
206     }
207     command += " -o " + inputTemp.getAbsolutePath() + SPACE
208             + hmmTemp.getAbsolutePath() + SPACE
209             + outTemp.getAbsolutePath();
210     return runCommand(command);
211   }
212
213   /**
214    * Imports the data from the temporary file to which the output of hmmalign is
215    * directed. this is used for an internal job.
216    * 
217    * @param index
218    *          The index of the 'job' (or region of an alignment).
219    * @throws IOException
220    * @throws InterruptedException
221    */
222   private void importData(int index)
223           throws IOException, InterruptedException
224   {
225     StockholmFile file = new StockholmFile(inputTemp.getAbsolutePath(),
226             DataSourceType.FILE);
227     SequenceI[] result = file.getSeqsAsArray();
228     AlignmentOrder msaorder = new AlignmentOrder(result);
229     AlignmentSorter.recoverOrder(result);
230     SeqsetUtils.deuniquify(hash, result);
231     allOrders.add(msaorder);
232     allResults[index] = result;
233     hmmTemp.delete();
234     outTemp.delete();
235     inputTemp.delete();
236   }
237
238   /**
239    * Gathers the sequences in preparation for the alignment.
240    */
241   private void prepareAlignment()
242   {
243     // hmmSeqs = alignment.getHMMConsensusSequences(true);
244     msa = af.gatherSequencesForAlignment();
245   }
246
247   /**
248    * Displays the results of all 'jobs'.
249    * 
250    * @param newFrame
251    */
252   private void displayResults(boolean newFrame)
253   {
254     AlignmentOrder[] arrOrders = allOrders
255             .toArray(new AlignmentOrder[allOrders.size()]);
256     Object[] newview = msa.getUpdatedView(allResults, arrOrders, '-');
257     SequenceI[] alignment = (SequenceI[]) newview[0];
258     HiddenColumns hidden = (HiddenColumns) newview[1];
259     Alignment al = new Alignment(alignment);
260     al.setProperty("Alignment Program", "hmmalign");
261     if (dataset != null)
262     {
263       al.setDataset(dataset);
264     }
265
266     if (newFrame)
267     {
268       displayInNewFrame(al, allOrders, hidden);
269     }
270   }
271
272   /**
273    * Displays the results in a new frame.
274    * 
275    * @param al
276    *          The alignment containing the results.
277    * @param alorders
278    *          The order of the sequences in the alignment on which the jobs were
279    *          run.
280    * @param hidden
281    *          Hidden columns in the previous alignment.
282    */
283   private void displayInNewFrame(AlignmentI al,
284           List<AlignmentOrder> alorders, HiddenColumns hidden)
285   {
286     AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
287             AlignFrame.DEFAULT_HEIGHT);
288
289     // initialise with same renderer settings as in parent alignframe.
290     af.getFeatureRenderer().transferSettings(this.featureSettings);
291
292     if (allOrders.size() > 0)
293     {
294       addSortByMenuItems(af, allOrders);
295     }
296
297     // TODO: refactor retrieve and show as new splitFrame as Desktop method
298
299     /*
300      * If alignment was requested from one half of a SplitFrame, show in a
301      * SplitFrame with the other pane similarly aligned.
302      */
303     AlignFrame requestedBy = this.af;
304     if (requestedBy != null && requestedBy.getSplitViewContainer() != null
305             && requestedBy.getSplitViewContainer()
306                     .getComplement(requestedBy) != null)
307     {
308       AlignmentI complement = requestedBy.getSplitViewContainer()
309               .getComplement(requestedBy);
310       String complementTitle = requestedBy.getSplitViewContainer()
311               .getComplementTitle(requestedBy);
312       // becomes null if the alignment window was closed before the alignment
313       // job finished.
314       AlignmentI copyComplement = new Alignment(complement);
315       // todo should this be done by copy constructor?
316       copyComplement.setGapCharacter(complement.getGapCharacter());
317       // share the same dataset (and the mappings it holds)
318       copyComplement.setDataset(complement.getDataset());
319       copyComplement.alignAs(al);
320       if (copyComplement.getHeight() > 0)
321       {
322         af.setTitle(this.af.getTitle());
323         AlignFrame af2 = new AlignFrame(copyComplement,
324                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
325         af2.setTitle(complementTitle);
326         String linkedTitle = MessageManager
327                 .getString("label.linked_view_title");
328         JInternalFrame splitFrame = new SplitFrame(
329                 al.isNucleotide() ? af : af2, al.isNucleotide() ? af2 : af);
330         Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
331         return;
332       }
333     }
334
335     /*
336      * Not from SplitFrame, or failed to created a complementary alignment
337      */
338     Desktop.addInternalFrame(af, af.getTitle(), AlignFrame.DEFAULT_WIDTH,
339             AlignFrame.DEFAULT_HEIGHT);
340   }
341
342   /**
343    * Add sort order options to the AlignFrame menus.
344    * 
345    * @param af
346    * @param alorders
347    */
348   protected void addSortByMenuItems(AlignFrame af,
349           List<AlignmentOrder> alorders)
350   {
351     // update orders
352     if (alorders.size() == 1)
353     {
354       af.addSortByOrderMenuItem("hmmalign" + " Ordering", alorders.get(0));
355     }
356     else
357     {
358       // construct a non-redundant ordering set
359       List<String> names = new ArrayList<>();
360       for (int i = 0, l = alorders.size(); i < l; i++)
361       {
362         String orderName = " Region " + i;
363         int j = i + 1;
364
365         while (j < l)
366         {
367           if (alorders.get(i).equals(alorders.get(j)))
368           {
369             alorders.remove(j);
370             l--;
371             orderName += "," + j;
372           }
373           else
374           {
375             j++;
376           }
377         }
378
379         if (i == 0 && j == 1)
380         {
381           names.add("");
382         }
383         else
384         {
385           names.add(orderName);
386         }
387       }
388       for (int i = 0, l = alorders.size(); i < l; i++)
389       {
390         af.addSortByOrderMenuItem("hmmalign" + (names.get(i)) + " Ordering",
391                 alorders.get(i));
392       }
393     }
394   }
395
396   /**
397    * Runs hmmalign, and waits for the results to be imported before continuing
398    */
399   public void hmmalignWaitTillComplete()
400   {
401     Thread loader = new Thread(this);
402     loader.start();
403
404     while (loader.isAlive())
405     {
406       try
407       {
408         Thread.sleep(500);
409       } catch (Exception ex)
410       {
411       }
412     }
413
414   }
415 }