983ba750e91b34ef4e6f584220ced30b21b34977
[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.Collections;
16 import java.util.EnumSet;
17 import java.util.List;
18 import java.util.stream.Collectors;
19
20 import jalview.bin.Console;
21
22 public class FileUtils
23 {
24   /*
25    * Given string glob pattern (see
26    * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
27    * ) return a List of Files that match the pattern.
28    * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities. 
29    */
30   public static List<File> getFilesFromGlob(String pattern)
31   {
32     return getFilesFromGlob(pattern, true);
33   }
34
35   public static List<File> getFilesFromGlob(String pattern,
36           boolean allowSingleFilenameThatDoesNotExist)
37   {
38     pattern = substituteHomeDir(pattern);
39     String relativePattern = pattern.startsWith(File.separator) ? null
40             : pattern;
41     List<File> files = new ArrayList<>();
42     /*
43      * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
44      * We look for the first glob character * { ? and then look for the last File.separator before that.
45      * Then we can reset the path to look at and shorten the globbing pattern.
46      * Relative paths can be used in pattern, which work from the pwd (though these are converted into
47      * full paths in the match). 
48      */
49     int firstGlobChar = -1;
50     boolean foundGlobChar = false;
51     for (char c : new char[] { '*', '{', '?' })
52     {
53       if (pattern.indexOf(c) > -1
54               && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
55       {
56         firstGlobChar = pattern.indexOf(c);
57         foundGlobChar = true;
58       }
59     }
60     int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
61     if (foundGlobChar)
62     {
63       String pS = pattern.substring(0, lastFS + 1);
64       String rest = pattern.substring(lastFS + 1);
65       if ("".equals(pS))
66       {
67         pS = ".";
68       }
69       Path parentDir = Paths.get(pS);
70       if (parentDir.toFile().exists())
71       {
72         try
73         {
74           String glob = "glob:" + parentDir.toString() + File.separator
75                   + rest;
76           PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
77           int maxDepth = rest.contains("**") ? 1028
78                   : (int) (rest.chars()
79                           .filter(ch -> ch == File.separatorChar).count())
80                           + 1;
81
82           Files.walkFileTree(parentDir,
83                   EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
84                   new SimpleFileVisitor<Path>()
85                   {
86                     @Override
87                     public FileVisitResult visitFile(Path path,
88                             BasicFileAttributes attrs) throws IOException
89                     {
90                       if (pm.matches(path))
91                       {
92                         files.add(path.toFile());
93                       }
94                       return FileVisitResult.CONTINUE;
95                     }
96
97                     @Override
98                     public FileVisitResult visitFileFailed(Path file,
99                             IOException exc) throws IOException
100                     {
101                       return FileVisitResult.CONTINUE;
102                     }
103                   });
104         } catch (IOException e)
105         {
106           e.printStackTrace();
107         }
108       }
109     }
110     else
111     {
112       // no wildcards
113       File f = new File(pattern);
114       if (allowSingleFilenameThatDoesNotExist || f.exists())
115       {
116         files.add(f);
117       }
118     }
119     Collections.sort(files);
120
121     return files;
122   }
123
124   public static List<String> getFilenamesFromGlob(String pattern)
125   {
126     // convert list of Files to list of File.getPath() Strings
127     return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
128             .collect(Collectors.toList());
129   }
130
131   public static String substituteHomeDir(String path)
132   {
133     return path.startsWith("~" + File.separator)
134             ? System.getProperty("user.home") + path.substring(1)
135             : path;
136   }
137
138   /*
139    * This method returns the basename of File file
140    */
141   public static String getBasename(File file)
142   {
143     return getBasenameOrExtension(file, false);
144   }
145
146   /*
147    * This method returns the extension of File file.
148    */
149   public static String getExtension(File file)
150   {
151     return getBasenameOrExtension(file, true);
152   }
153
154   public static String getBasenameOrExtension(File file, boolean extension)
155   {
156     if (file == null)
157       return null;
158
159     String value = null;
160     String filename = file.getName();
161     int lastDot = filename.lastIndexOf('.');
162     if (lastDot > 0) // don't truncate if starts with '.'
163     {
164       value = extension ? filename.substring(lastDot + 1)
165               : filename.substring(0, lastDot);
166     }
167     else
168     {
169       value = extension ? "" : filename;
170     }
171     return value;
172   }
173
174   /*
175    * This method returns the dirname of the first --append or --open value. 
176    * Used primarily for substitutions in output filenames.
177    */
178   public static String getDirname(File file)
179   {
180     if (file == null)
181       return null;
182
183     String dirname = null;
184     File p = file.getParentFile();
185     if (p == null)
186     {
187       p = new File(".");
188     }
189     File d = new File(substituteHomeDir(p.getPath()));
190     dirname = d.getPath();
191     return dirname;
192   }
193
194   public static String convertWildcardsToPath(String value, String wildcard,
195           String dirname, String basename)
196   {
197     if (value == null)
198     {
199       return null;
200     }
201     StringBuilder path = new StringBuilder();
202     int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
203     int wildcardBeforeIndex = value.indexOf(wildcard);
204     if (lastFileSeparatorIndex > wildcard.length() - 1
205             && wildcardBeforeIndex < lastFileSeparatorIndex)
206     {
207       path.append(value.substring(0, wildcardBeforeIndex));
208       path.append(dirname);
209       path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
210               lastFileSeparatorIndex + 1));
211     }
212     else
213     {
214       path.append(value.substring(0, lastFileSeparatorIndex + 1));
215     }
216     int wildcardAfterIndex = value.indexOf(wildcard,
217             lastFileSeparatorIndex);
218     if (wildcardAfterIndex > lastFileSeparatorIndex)
219     {
220       path.append(value.substring(lastFileSeparatorIndex + 1,
221               wildcardAfterIndex));
222       path.append(basename);
223       path.append(value.substring(wildcardAfterIndex + wildcard.length()));
224     }
225     else
226     {
227       path.append(value.substring(lastFileSeparatorIndex + 1));
228     }
229     return path.toString();
230   }
231
232   public static File getParentDir(File file)
233   {
234     if (file == null)
235     {
236       return null;
237     }
238     File parentDir = file.getAbsoluteFile().getParentFile();
239     return parentDir;
240   }
241
242   public static boolean checkParentDir(File file, boolean mkdirs)
243   {
244     if (file == null)
245     {
246       return false;
247     }
248     File parentDir = getParentDir(file);
249     if (parentDir.exists())
250     {
251       // already exists, nothing to do so nothing to worry about!
252       return true;
253     }
254
255     if (!mkdirs)
256     {
257       return false;
258     }
259
260     Path path = file.toPath();
261     for (int i = 0; i < path.getNameCount(); i++)
262     {
263       Path p = path.getName(i);
264       if ("..".equals(p.toString()))
265       {
266         Console.warn("Cautiously not running mkdirs on " + file.toString()
267                 + " because the path to be made contains '..'");
268         return false;
269       }
270     }
271
272     return parentDir.mkdirs();
273   }
274 }