JAL-2994 utility method to match a list of folder/file patterns
authorMungo Carstairs <gmungoc@gmail.com>
Tue, 29 May 2018 10:24:00 +0000 (11:24 +0100)
committerMungo Carstairs <gmungoc@gmail.com>
Tue, 29 May 2018 10:24:00 +0000 (11:24 +0100)
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/util/FileUtils.java
test/jalview/util/FileUtilsTest.java

index c278db5..b90a5a1 100644 (file)
@@ -38,7 +38,6 @@ import jalview.util.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -940,22 +939,12 @@ public class StructureManager
        * typical Windows installation path is
        * C:\Program Files\Chimera 1.12\bin\chimera.exe
        */
-      for (String root : new String[] { "\\Program Files",
-          "C:\\Program Files", "\\Program Files (x86)",
-          "C:\\Program Files (x86)" })
-      {
-        /*
-         * match a path ending in \bin\chimera or \bin\chimera.exe
-         * back-slashes are double escaped - for Java String, and for regex
-         */
-        for (String version : FileUtils
-                .findMatchingPaths(".*\\\\Chimera.*$", Paths.get(root)))
-        {
-          pathList.addAll(FileUtils.findMatchingPaths(
-                  ".*\\\\bin\\\\chimera(\\\\.exe)?$",
-                  Paths.get(version)));
-        }
-      }
+      // current drive:
+      pathList.addAll(FileUtils.findMatches("\\",
+              "Program Files*/Chimera*/bin/{chimera,chimera.exe}"));
+      // C: drive (note may add as duplicates)
+      pathList.addAll(FileUtils.findMatches("C:\\",
+              "Program Files*/Chimera*/bin/{chimera,chimera.exe}"));
     }
     else if (os.startsWith("Mac"))
     {
index 3652448..7e607ab 100644 (file)
@@ -2,11 +2,14 @@ package jalview.util;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.DirectoryStream;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -85,24 +88,26 @@ public final class FileUtils
    * Answers a (possibly empty) list of file paths found by searching below
    * <code>from</code> that match the supplied regular expression
    * <code>pattern</code>. Results may include <code>from</code> itself if it
-   * matches. If an exception occurs it is written to syserr and any results up
-   * to that point are returned. Note that the regular expression match applies
-   * to the whole path of any matched file.
+   * matches. If an exception occurs it is written to syserr and any results up to
+   * that point are returned. Note that the regular expression match applies to
+   * the whole path of any matched file.
    * <p>
-   * Because the whole directory tree below <code>from</code> is searched, this
-   * method may be slow if used for a high level directory.
+   * WARNING: because the whole directory tree below <code>from</code> is
+   * searched, this method may be slow if used for a high level directory, or may
+   * exit prematurely if security or other exceptions occur.
    * 
    * <pre>
    * Example: 
-   *   findMatchingPaths(".*&#47chimera.exe$", Paths.get("C:/Program Files"))
+   *   findMatchingPaths(Paths.get("C:/Program Files"), ".*&#47chimera.exe$")
    * </pre>
    * 
-   * @param pattern
    * @param from
+   * @param pattern
+   * 
    * @return
    * @see https://stackoverflow.com/questions/794381/how-to-find-files-that-match-a-wildcard-string-in-java/31685610#comment62441832_31685610
    */
-  public static List<String> findMatchingPaths(String pattern, Path from)
+  public static List<String> findMatchingPaths(Path from, String pattern)
   {
     List<String> matches = new ArrayList<>();
     PathMatcher pathMatcher = FileSystems.getDefault()
@@ -119,4 +124,85 @@ public final class FileUtils
 
     return matches;
   }
+
+  /**
+   * Answers a (possibly empty) list of paths to files below the given root path,
+   * that match the given pattern. The pattern should be a '/' delimited set of
+   * glob patterns, each of which is used to match child file names (not full
+   * paths). Note that 'directory spanning' glob patterns (**) are <em>not</em>
+   * supported by this method.
+   * <p>
+   * For example
+   * 
+   * <pre>
+   *   findMatches("C:\\", "Program Files*&#47Chimera*&#47bin/{chimera,chimera.exe}"
+   * </pre>
+   * 
+   * would match "C:\Program Files\Chimera 1.11\bin\chimera.exe" and "C:\Program
+   * Files (x86)\Chimera 1.10.1\bin\chimera"
+   * 
+   * @param root
+   * @param pattern
+   * @return
+   * @see https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob
+   */
+  public static List<String> findMatches(String root, String pattern)
+  {
+    List<String> results = new ArrayList<>();
+    try
+    {
+      Path from = Paths.get(root);
+      findMatches(results, from, Arrays.asList(pattern.split("/")));
+    } catch (Throwable e)
+    {
+      // Paths.get can throw IllegalArgumentException
+      System.err.println(String.format("Error searching %s for %s: %s",
+              root, pattern, e.toString()));
+    }
+
+    return results;
+  }
+
+  /**
+   * A helper method that performs recursive search of file patterns and adds any
+   * 'leaf node' matches to the results list
+   * 
+   * @param results
+   * @param from
+   * @param patterns
+   */
+  protected static void findMatches(List<String> results, Path from,
+          List<String> patterns)
+  {
+    if (patterns.isEmpty())
+    {
+      /*
+       * reached end of recursion with all components matched
+       */
+      results.add(from.toString());
+      return;
+    }
+
+    String pattern = patterns.get(0);
+    try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(from,
+            pattern))
+    {
+      dirStream.forEach(p -> {
+
+        /*
+         * matched a next level file - search below it
+         * (ignore non-directory non-leaf matches)
+         */
+        List<String> subList = patterns.subList(1, patterns.size());
+        if (subList.isEmpty() || p.toFile().isDirectory())
+        {
+          findMatches(results, p, subList);
+        }
+      });
+    } catch (IOException e)
+    {
+      System.err.println(String.format("Error searching %s: %s", pattern,
+              e.toString()));
+    }
+  }
 }
index aed9142..07fd375 100644 (file)
@@ -1,5 +1,6 @@
 package jalview.util;
 
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
@@ -13,7 +14,7 @@ public class FileUtilsTest
   @Test(groups = "Functional")
   public void testFindMatchingPaths() throws IOException
   {
-    String expect1 = Paths.get("../jalview/examples/plantfdx.fa")
+    String expect1 = Paths.get("..", "jalview", "examples", "plantfdx.fa")
             .toString();
     String expect2 = Paths.get("../jalview/examples/plantfdx.features")
             .toString();
@@ -21,10 +22,52 @@ public class FileUtilsTest
             .get("../jalview/examples/testdata/plantfdx.features")
             .toString();
 
-    List<String> matches = FileUtils.findMatchingPaths(".*/plant.*\\.f.*",
-            Paths.get(".."));
+    List<String> matches = FileUtils
+            .findMatchingPaths(Paths.get(".."),
+            ".*[/\\\\]plant.*\\.f.*");
+    System.out.println(matches);
     assertTrue(matches.contains(expect1));
     assertTrue(matches.contains(expect2));
     assertTrue(matches.contains(expect3));
   }
+
+  @Test(groups = "External")
+  public void testWindowsPath() throws IOException
+  {
+    if (System.getProperty("os.name").startsWith("Windows"))
+    {
+      /*
+       * should pass provided Eclipse is installed
+       */
+      List<String> matches = FileUtils.findMatches("C:\\",
+              "Program Files*/eclips*/eclips?.exe");
+      assertFalse(matches.isEmpty());
+
+      /*
+       * should pass provided Chimera is installed
+       */
+      matches = FileUtils.findMatches("C:\\",
+              "Program Files*/Chimera*/bin/{chimera,chimera.exe}");
+      assertFalse(matches.isEmpty());
+    }
+  }
+
+  @Test(groups = "Functional")
+  public void testFindMatches() throws IOException
+  {
+    String expect1 = Paths.get("..", "jalview", "examples", "plantfdx.fa")
+            .toString();
+    String expect2 = Paths.get("../jalview/examples/plantfdx.features")
+            .toString();
+    String expect3 = Paths
+            .get("../jalview/examples/testdata/plantfdx.features")
+            .toString();
+  
+    List<String> matches = FileUtils
+            .findMatches("..", "jalview/ex*/plant*.f*");
+    System.out.println(matches);
+    assertTrue(matches.contains(expect1));
+    assertTrue(matches.contains(expect2));
+    assertFalse(matches.contains(expect3));
+  }
 }