Merge branch 'develop' into update_212_Dec_merge_with_21125_chamges
[jalview.git] / src / jalview / hmmer / HmmerCommand.java
index fe6c0f9..9db0ae1 100644 (file)
@@ -1,20 +1,26 @@
 package jalview.hmmer;
 
 import jalview.analysis.SeqsetUtils;
+import jalview.analysis.SeqsetUtils.SequenceInfo;
 import jalview.bin.Cache;
+import jalview.bin.Console;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.HiddenMarkovModel;
+import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.Preferences;
+import jalview.io.FastaFile;
 import jalview.io.HMMFile;
 import jalview.io.StockholmFile;
+import jalview.util.FileUtils;
 import jalview.util.MessageManager;
+import jalview.util.Platform;
 import jalview.ws.params.ArgumentI;
 
 import java.io.BufferedReader;
@@ -22,8 +28,11 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
+import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Base class for hmmbuild, hmmalign and hmmsearch
@@ -41,6 +50,53 @@ public abstract class HmmerCommand implements Runnable
 
   protected final List<ArgumentI> params;
 
+  /*
+   * constants for i18n lookup of passed parameter names
+   */
+  static final String DATABASE_KEY = "label.database";
+
+  static final String THIS_ALIGNMENT_KEY = "label.this_alignment";
+
+  static final String USE_ACCESSIONS_KEY = "label.use_accessions";
+
+  static final String AUTO_ALIGN_SEQS_KEY = "label.auto_align_seqs";
+
+  static final String NUMBER_OF_RESULTS_KEY = "label.number_of_results";
+
+  static final String NUMBER_OF_ITERATIONS = "label.number_of_iterations";
+
+  static final String TRIM_TERMINI_KEY = "label.trim_termini";
+
+  static final String RETURN_N_NEW_SEQ = "label.check_for_new_sequences";
+
+  static final String REPORTING_CUTOFF_KEY = "label.reporting_cutoff";
+
+  static final String CUTOFF_NONE = "label.default";
+
+  static final String CUTOFF_SCORE = "label.score";
+
+  static final String CUTOFF_EVALUE = "label.evalue";
+
+  static final String REPORTING_SEQ_EVALUE_KEY = "label.reporting_seq_evalue";
+
+  static final String REPORTING_DOM_EVALUE_KEY = "label.reporting_dom_evalue";
+
+  static final String REPORTING_SEQ_SCORE_KEY = "label.reporting_seq_score";
+
+  static final String REPORTING_DOM_SCORE_KEY = "label.reporting_dom_score";
+
+  static final String INCLUSION_SEQ_EVALUE_KEY = "label.inclusion_seq_evalue";
+
+  static final String INCLUSION_DOM_EVALUE_KEY = "label.inclusion_dom_evalue";
+
+  static final String INCLUSION_SEQ_SCORE_KEY = "label.inclusion_seq_score";
+
+  static final String INCLUSION_DOM_SCORE_KEY = "label.inclusion_dom_score";
+
+  static final String ARG_TRIM = "--trim";
+
+  static final String INCLUSION_THRESHOLD_KEY = "label.inclusion_threshold";
+
   /**
    * Constructor
    * 
@@ -63,7 +119,8 @@ public abstract class HmmerCommand implements Runnable
    */
   public static boolean isHmmerAvailable()
   {
-    File exec = getExecutable(HMMBUILD, Cache.getProperty(Preferences.HMMER_PATH));
+    File exec = FileUtils.getExecutable(HMMBUILD,
+            Cache.getProperty(Preferences.HMMER_PATH));
     return exec != null;
   }
 
@@ -73,7 +130,7 @@ public abstract class HmmerCommand implements Runnable
    * 
    * @param seqs
    */
-  protected Hashtable stashSequences(SequenceI[] seqs)
+  protected Map<String, SequenceInfo> stashSequences(SequenceI[] seqs)
   {
     return SeqsetUtils.uniquify(seqs, true);
   }
@@ -81,29 +138,38 @@ public abstract class HmmerCommand implements Runnable
   /**
    * Restores the sequence data lost by uniquifying
    * 
-   * @param hashtable
+   * @param sequencesHash
    * @param seqs
    */
-  protected void recoverSequences(Hashtable hashtable, SequenceI[] seqs)
+  protected void recoverSequences(Map<String, SequenceInfo> sequencesHash, SequenceI[] seqs)
   {
-    SeqsetUtils.deuniquify(hashtable, seqs);
+    SeqsetUtils.deuniquify(sequencesHash, seqs);
   }
 
   /**
    * Runs a command as a separate process and waits for it to complete. Answers
    * true if the process return status is zero, else false.
    * 
-   * @param command
+   * @param commands
    *          the executable command and any arguments to it
    * @throws IOException
    */
-  public boolean runCommand(List<String> command)
+  public boolean runCommand(List<String> commands)
           throws IOException
   {
+    List<String> args = Platform.isWindowsAndNotJS() ? wrapWithCygwin(commands)
+            : commands;
+
     try
     {
-      ProcessBuilder pb = new ProcessBuilder(command);
+      ProcessBuilder pb = new ProcessBuilder(args);
       pb.redirectErrorStream(true); // merge syserr to sysout
+      if (Platform.isWindowsAndNotJS())
+      {
+        String path = pb.environment().get("Path");
+        path = jalview.bin.Cache.getProperty("CYGWIN_PATH") + ";" + path;
+        pb.environment().put("Path", path);
+      }
       final Process p = pb.start();
       new Thread(new Runnable()
       {
@@ -128,7 +194,13 @@ public abstract class HmmerCommand implements Runnable
       }).start();
 
       p.waitFor();
-      return p.exitValue() == 0; // 0 is success, by convention
+      int exitValue = p.exitValue();
+      if (exitValue != 0)
+      {
+        Console.error("Command failed, return code = " + exitValue);
+        Console.error("Command/args were: " + args.toString());
+      }
+      return exitValue == 0; // 0 is success, by convention
     } catch (Exception e)
     {
       e.printStackTrace();
@@ -137,8 +209,43 @@ public abstract class HmmerCommand implements Runnable
   }
 
   /**
+   * Converts the given command to a Cygwin "bash" command wrapper. The hmmer
+   * command and any arguments to it are converted into a single parameter to the
+   * bash command.
+   * 
+   * @param commands
+   */
+  protected List<String> wrapWithCygwin(List<String> commands)
+  {
+    File bash = FileUtils.getExecutable("bash",
+            Cache.getProperty(Preferences.CYGWIN_PATH));
+    if (bash == null)
+    {
+      Console.error("Cygwin shell not found");
+      return commands;
+    }
+
+    List<String> wrapped = new ArrayList<>();
+    // wrapped.add("C:\Users\tva\run");
+    wrapped.add(bash.getAbsolutePath());
+    wrapped.add("-c");
+
+    /*
+     * combine hmmbuild/search/align and arguments to a single string
+     */
+    StringBuilder sb = new StringBuilder();
+    for (String cmd : commands)
+    {
+      sb.append(" ").append(cmd);
+    }
+    wrapped.add(sb.toString());
+
+    return wrapped;
+  }
+
+  /**
    * Exports an alignment, and reference (RF) annotation if present, to the
-   * specified file, in Stockholm format
+   * specified file, in Stockholm format, removing all HMM sequences
    * 
    * @param seqs
    * @param toFile
@@ -146,13 +253,15 @@ public abstract class HmmerCommand implements Runnable
    * @throws IOException
    */
   public void exportStockholm(SequenceI[] seqs, File toFile,
-          AnnotatedCollectionI annotated) throws IOException
+          AnnotatedCollectionI annotated)
+          throws IOException
   {
     if (seqs == null)
     {
       return;
     }
     AlignmentI newAl = new Alignment(seqs);
+
     if (!newAl.isAligned())
     {
       newAl.padGaps();
@@ -186,6 +295,17 @@ public abstract class HmmerCommand implements Runnable
       }
     }
 
+    for (SequenceI seq : newAl.getSequencesArray())
+    {
+      if (seq.getAnnotation() != null)
+      {
+        for (AlignmentAnnotation ann : seq.getAnnotation())
+        {
+          seq.removeAlignmentAnnotation(ann);
+        }
+      }
+    }
+
     StockholmFile file = new StockholmFile(newAl);
     String output = file.print(seqs, false);
     PrintWriter writer = new PrintWriter(toFile);
@@ -200,81 +320,220 @@ public abstract class HmmerCommand implements Runnable
    * @param cmd
    *          command short name e.g. hmmalign
    * @return
+   * @throws IOException
    */
   protected String getCommandPath(String cmd)
+          throws IOException
   {
     String binariesFolder = Cache.getProperty(Preferences.HMMER_PATH);
-    File file = getExecutable(cmd, binariesFolder);
+    // ensure any symlink to the directory is resolved:
+    binariesFolder = Paths.get(binariesFolder).toRealPath().toString();
+    File file = FileUtils.getExecutable(cmd, binariesFolder);
     if (file == null && af != null)
     {
-        JvOptionPane.showInternalMessageDialog(af,
-                MessageManager.getString("warn.hmm_command_failed"));
+      JvOptionPane.showInternalMessageDialog(af, MessageManager
+              .formatMessage("label.executable_not_found", cmd));
     }
 
-    return file == null ? null : file.getAbsolutePath();
+    return file == null ? null : getFilePath(file, true);
   }
 
   /**
-   * Answers the executable file for the given hmmer command, or null if not
-   * found or not executable. The path to the executable is the command name
-   * prefixed by the hmmer binaries folder path, optionally with .exe appended.
+   * Exports an HMM to the specified file
+   * 
+   * @param hmm
+   * @param hmmFile
+   * @throws IOException
+   */
+  public void exportHmm(HiddenMarkovModel hmm, File hmmFile)
+          throws IOException
+  {
+    if (hmm != null)
+    {
+      HMMFile file = new HMMFile(hmm);
+      PrintWriter writer = new PrintWriter(hmmFile);
+      writer.print(file.print());
+      writer.close();
+    }
+  }
+
+  // TODO is needed?
+  /**
+   * Exports a sequence to the specified file
+   * 
+   * @param hmm
+   * @param hmmFile
+   * @throws IOException
+   */
+  public void exportSequence(SequenceI seq, File seqFile) throws IOException
+  {
+    if (seq != null)
+    {
+      FastaFile file = new FastaFile();
+      PrintWriter writer = new PrintWriter(seqFile);
+      writer.print(file.print(new SequenceI[] { seq }, false));
+      writer.close();
+    }
+  }
+
+  /**
+   * Answers the HMM profile for the profile sequence the user selected (default
+   * is just the first HMM sequence in the alignment)
    * 
-   * @param cmd
-   *          hmmer command short name, for example hmmbuild
-   * @param binaryPath
-   *          parent folder containing hmmer executables
    * @return
    */
-  public static File getExecutable(String cmd, String binaryPath)
+  protected HiddenMarkovModel getHmmProfile()
   {
-    File file = new File(binaryPath, cmd);
-    if (!file.canExecute())
+    String alignToParamName = MessageManager.getString("label.use_hmm");
+    for (ArgumentI arg : params)
     {
-      file = new File(binaryPath, cmd + ".exe");
+      String name = arg.getName();
+      if (name.equals(alignToParamName))
       {
-        if (!file.canExecute())
+        String seqName = arg.getValue();
+        SequenceI hmmSeq = alignment.findName(seqName);
+        if (hmmSeq.hasHMMProfile())
         {
-          file = null;
+          return hmmSeq.getHMM();
         }
       }
     }
-    return file;
+    return null;
   }
 
   /**
-   * A convenience method to create a temporary file that is deleted on exit of
-   * the JVM
+   * Answers the query sequence the user selected (default is just the first
+   * sequence in the alignment)
    * 
-   * @param prefix
-   * @param suffix
    * @return
-   * @throws IOException
    */
-  protected File createTempFile(String prefix, String suffix)
-          throws IOException
+  protected SequenceI getSequence()
   {
-    File f = File.createTempFile(prefix, suffix);
-    f.deleteOnExit();
-    return f;
+    String alignToParamName = MessageManager
+            .getString("label.use_sequence");
+    for (ArgumentI arg : params)
+    {
+      String name = arg.getName();
+      if (name.equals(alignToParamName))
+      {
+        String seqName = arg.getValue();
+        SequenceI seq = alignment.findName(seqName);
+        return seq;
+      }
+    }
+    return null;
+  }
 
+  /**
+   * Answers an absolute path to the given file, in a format suitable for
+   * processing by a hmmer command. On a Windows platform, the native Windows file
+   * path is converted to Cygwin format, by replacing '\'with '/' and drive letter
+   * X with /cygdrive/x.
+   * 
+   * @param resultFile
+   * @param isInCygwin
+   *                     True if file is to be read/written from within the Cygwin
+   *                     shell. Should be false for any imports.
+   * @return
+   */
+  protected String getFilePath(File resultFile, boolean isInCygwin)
+  {
+    String path = resultFile.getAbsolutePath();
+    if (Platform.isWindowsAndNotJS() && isInCygwin)
+    {
+      // the first backslash escapes '\' for the regular expression argument
+      path = path.replaceAll("\\" + File.separator, "/");
+      int colon = path.indexOf(':');
+      if (colon > 0)
+      {
+        String drive = path.substring(0, colon);
+        path = path.replaceAll(drive + ":", "/cygdrive/" + drive);
+      }
+    }
+
+    return path;
   }
 
   /**
-   * Exports an HMM to the specified file
+   * A helper method that deletes any HMM consensus sequence from the given
+   * collection, and from the parent alignment if <code>ac</code> is a subgroup
    * 
-   * @param hmm
-   * @param hmmFile
-   * @throws IOException
+   * @param ac
    */
-  public void exportHmm(HiddenMarkovModel hmm, File hmmFile)
-          throws IOException
+  void deleteHmmSequences(AnnotatedCollectionI ac)
   {
-    if (hmm != null)
+    List<SequenceI> hmmSeqs = ac.getHmmSequences();
+    for (SequenceI hmmSeq : hmmSeqs)
     {
-      HMMFile file = new HMMFile(hmm);
-      PrintWriter writer = new PrintWriter(hmmFile);
-      writer.print(file.print());
-      writer.close();
+      if (ac instanceof SequenceGroup)
+      {
+        ((SequenceGroup) ac).deleteSequence(hmmSeq, false);
+        AnnotatedCollectionI context = ac.getContext();
+        if (context != null && context instanceof AlignmentI)
+        {
+          ((AlignmentI) context).deleteSequence(hmmSeq);
+        }
+      }
+      else
+      {
+        ((AlignmentI) ac).deleteSequence(hmmSeq);
+      }
     }
   }
+
+  /**
+   * Sets the names of any duplicates within the given sequences to include their
+   * respective lengths. Deletes any duplicates that have the same name after this
+   * step
+   * 
+   * @param seqs
+   */
+  void renameDuplicates(AlignmentI al)
+  {
+
+    SequenceI[] seqs = al.getSequencesArray();
+    List<Boolean> wasRenamed = new ArrayList<>();
+
+    for (SequenceI seq : seqs)
+    {
+      wasRenamed.add(false);
+    }
+
+    for (int i = 0; i < seqs.length; i++)
+    {
+      for (int j = 0; j < seqs.length; j++)
+      {
+        if (seqs[i].getName().equals(seqs[j].getName()) && i != j
+                && !wasRenamed.get(j))
+        {
+
+          wasRenamed.set(i, true);
+          String range = "/" + seqs[j].getStart() + "-" + seqs[j].getEnd();
+          // setting sequence name to include range - to differentiate between
+          // sequences of the same name. Currently have to include the range twice
+          // because the range is removed (once) when setting the name
+          // TODO come up with a better way of doing this
+          seqs[j].setName(seqs[j].getName() + range + range);
+        }
+
+      }
+      if (wasRenamed.get(i))
+      {
+        String range = "/" + seqs[i].getStart() + "-" + seqs[i].getEnd();
+        seqs[i].setName(seqs[i].getName() + range + range);
+      }
+    }
+
+    for (int i = 0; i < seqs.length; i++)
+    {
+      for (int j = 0; j < seqs.length; j++)
+      {
+        if (seqs[i].getName().equals(seqs[j].getName()) && i != j)
+        {
+          al.deleteSequence(j);
+        }
+      }
+    }
+  }
+
 }