JAL-629 added setprop. More filename based substitutions enabled. Test for these.
[jalview.git] / src / jalview / util / FileUtils.java
1 package jalview.util;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.nio.file.FileSystems;
6 import java.nio.file.FileVisitOption;
7 import java.nio.file.FileVisitResult;
8 import java.nio.file.Files;
9 import java.nio.file.Path;
10 import java.nio.file.PathMatcher;
11 import java.nio.file.Paths;
12 import java.nio.file.SimpleFileVisitor;
13 import java.nio.file.attribute.BasicFileAttributes;
14 import java.util.ArrayList;
15 import java.util.EnumSet;
16 import java.util.List;
17 import java.util.stream.Collectors;
18
19 import jalview.bin.Console;
20
21 public class FileUtils
22 {
23   /*
24    * Given string glob pattern (see
25    * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
26    * ) return a List of Files that match the pattern.
27    * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities. 
28    */
29   public static List<File> getFilesFromGlob(String pattern)
30   {
31     return getFilesFromGlob(pattern, true);
32   }
33
34   public static List<File> getFilesFromGlob(String pattern,
35           boolean allowSingleFilenameThatDoesNotExist)
36   {
37     pattern = substituteHomeDir(pattern);
38     List<File> files = new ArrayList<>();
39     /*
40      * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
41      * We look for the first glob character * { ? and then look for the last File.separator before that.
42      * Then we can reset the path to look at and shorten the globbing pattern.
43      * Relative paths can be used in pattern, which work from the pwd (though these are converted into
44      * full paths in the match). 
45      */
46     int firstGlobChar = -1;
47     boolean foundGlobChar = false;
48     for (char c : new char[] { '*', '{', '?' })
49     {
50       if (pattern.indexOf(c) > -1
51               && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
52       {
53         firstGlobChar = pattern.indexOf(c);
54         foundGlobChar = true;
55       }
56     }
57     int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
58     if (foundGlobChar)
59     {
60       String pS = pattern.substring(0, lastFS + 1);
61       String rest = pattern.substring(lastFS + 1);
62       Path parentDir = Paths.get(pS).toAbsolutePath();
63       if (parentDir.toFile().exists())
64       {
65         try
66         {
67           String glob = "glob:" + parentDir.toString() + File.separator
68                   + rest;
69           PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
70           int maxDepth = rest.contains("**") ? Integer.MAX_VALUE
71                   : (int) (rest.chars()
72                           .filter(ch -> ch == File.separatorChar).count())
73                           + 1;
74           Files.walkFileTree(parentDir,
75                   EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
76                   new SimpleFileVisitor<Path>()
77                   {
78                     @Override
79                     public FileVisitResult visitFile(Path path,
80                             BasicFileAttributes attrs) throws IOException
81                     {
82                       if (pm.matches(path))
83                       {
84                         files.add(path.toFile());
85                       }
86                       return FileVisitResult.CONTINUE;
87                     }
88
89                     @Override
90                     public FileVisitResult visitFileFailed(Path file,
91                             IOException exc) throws IOException
92                     {
93                       return FileVisitResult.CONTINUE;
94                     }
95                   });
96         } catch (IOException e)
97         {
98           e.printStackTrace();
99         }
100       }
101     }
102     else
103     {
104       // no wildcards
105       File f = new File(pattern);
106       if (allowSingleFilenameThatDoesNotExist || f.exists())
107       {
108         files.add(f);
109       }
110     }
111
112     return files;
113   }
114
115   public static List<String> getFilenamesFromGlob(String pattern)
116   {
117     // convert list of Files to list of File.getPath() Strings
118     return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
119             .collect(Collectors.toList());
120   }
121
122   public static String substituteHomeDir(String path)
123   {
124     return path.startsWith("~" + File.separator)
125             ? System.getProperty("user.home") + path.substring(1)
126             : path;
127   }
128
129   /*
130    * This method returns the basename of the first --open or --opennew value. 
131    * Used primarily for substitutions in output filenames.
132    */
133   public static String getBasename(File file)
134   {
135     if (file == null)
136       return null;
137
138     String basename = null;
139     String filename = file.getName();
140     int lastDot = filename.lastIndexOf('.');
141     if (lastDot > 0) // don't truncate if starts with '.'
142     {
143       basename = filename.substring(0, lastDot);
144     }
145     else
146     {
147       basename = filename;
148     }
149     return basename;
150   }
151
152   /*
153    * This method returns the dirname of the first --open or --opennew value. 
154    * Used primarily for substitutions in output filenames.
155    */
156   public static String getDirname(File file)
157   {
158     if (file == null)
159       return null;
160
161     String dirname = null;
162     try
163     {
164       dirname = file.getParentFile().getCanonicalPath();
165     } catch (IOException e)
166     {
167       Console.debug(
168               "Exception when getting dirname of '" + file.getPath() + "'",
169               e);
170       dirname = "";
171     }
172     return dirname;
173   }
174 }