From 1deb45d06fc8fa719bd19b25959397d8b3fd9274 Mon Sep 17 00:00:00 2001 From: Mungo Carstairs Date: Tue, 29 May 2018 11:24:00 +0100 Subject: [PATCH] JAL-2994 utility method to match a list of folder/file patterns --- .../edu/ucsf/rbvi/strucviz2/StructureManager.java | 23 ++--- src/jalview/util/FileUtils.java | 102 ++++++++++++++++++-- test/jalview/util/FileUtilsTest.java | 49 +++++++++- 3 files changed, 146 insertions(+), 28 deletions(-) diff --git a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java index c278db5..b90a5a1 100644 --- a/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java +++ b/src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java @@ -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")) { diff --git a/src/jalview/util/FileUtils.java b/src/jalview/util/FileUtils.java index 3652448..7e607ab 100644 --- a/src/jalview/util/FileUtils.java +++ b/src/jalview/util/FileUtils.java @@ -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 * from that match the supplied regular expression * pattern. Results may include from 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. *

- * Because the whole directory tree below from is searched, this - * method may be slow if used for a high level directory. + * WARNING: because the whole directory tree below from is + * searched, this method may be slow if used for a high level directory, or may + * exit prematurely if security or other exceptions occur. * *

    * Example: 
-   *   findMatchingPaths(".*/chimera.exe$", Paths.get("C:/Program Files"))
+   *   findMatchingPaths(Paths.get("C:/Program Files"), ".*/chimera.exe$")
    * 
* - * @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 findMatchingPaths(String pattern, Path from) + public static List findMatchingPaths(Path from, String pattern) { List 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 not + * supported by this method. + *

+ * For example + * + *

+   *   findMatches("C:\\", "Program Files*/Chimera*/bin/{chimera,chimera.exe}"
+   * 
+ * + * 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 findMatches(String root, String pattern) + { + List 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 results, Path from, + List patterns) + { + if (patterns.isEmpty()) + { + /* + * reached end of recursion with all components matched + */ + results.add(from.toString()); + return; + } + + String pattern = patterns.get(0); + try (DirectoryStream dirStream = Files.newDirectoryStream(from, + pattern)) + { + dirStream.forEach(p -> { + + /* + * matched a next level file - search below it + * (ignore non-directory non-leaf matches) + */ + List 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())); + } + } } diff --git a/test/jalview/util/FileUtilsTest.java b/test/jalview/util/FileUtilsTest.java index aed9142..07fd375 100644 --- a/test/jalview/util/FileUtilsTest.java +++ b/test/jalview/util/FileUtilsTest.java @@ -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 matches = FileUtils.findMatchingPaths(".*/plant.*\\.f.*", - Paths.get("..")); + List 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 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 matches = FileUtils + .findMatches("..", "jalview/ex*/plant*.f*"); + System.out.println(matches); + assertTrue(matches.contains(expect1)); + assertTrue(matches.contains(expect2)); + assertFalse(matches.contains(expect3)); + } } -- 1.7.10.2