Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
FileUtils | 46 | 176 | 75 |
1 | /* | |
2 | * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) | |
3 | * Copyright (C) $$Year-Rel$$ The Jalview Authors | |
4 | * | |
5 | * This file is part of Jalview. | |
6 | * | |
7 | * Jalview is free software: you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License | |
9 | * as published by the Free Software Foundation, either version 3 | |
10 | * of the License, or (at your option) any later version. | |
11 | * | |
12 | * Jalview is distributed in the hope that it will be useful, but | |
13 | * WITHOUT ANY WARRANTY; without even the implied warranty | |
14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR | |
15 | * PURPOSE. See the GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with Jalview. If not, see <http://www.gnu.org/licenses/>. | |
19 | * The Jalview Authors are detailed in the 'AUTHORS' file. | |
20 | */ | |
21 | package jalview.util; | |
22 | ||
23 | import java.io.File; | |
24 | import java.io.IOException; | |
25 | import java.net.MalformedURLException; | |
26 | import java.net.URL; | |
27 | import java.nio.file.FileSystems; | |
28 | import java.nio.file.FileVisitOption; | |
29 | import java.nio.file.FileVisitResult; | |
30 | import java.nio.file.Files; | |
31 | import java.nio.file.Path; | |
32 | import java.nio.file.PathMatcher; | |
33 | import java.nio.file.Paths; | |
34 | import java.nio.file.SimpleFileVisitor; | |
35 | import java.nio.file.attribute.BasicFileAttributes; | |
36 | import java.util.ArrayList; | |
37 | import java.util.Collections; | |
38 | import java.util.EnumSet; | |
39 | import java.util.HashSet; | |
40 | import java.util.List; | |
41 | import java.util.Set; | |
42 | import java.util.stream.Collectors; | |
43 | ||
44 | import jalview.bin.Console; | |
45 | ||
46 | public class FileUtils | |
47 | { | |
48 | /* | |
49 | * Given string glob pattern (see | |
50 | * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String) | |
51 | * ) return a List of Files that match the pattern. | |
52 | * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities. | |
53 | */ | |
54 | 164 | public static List<File> getFilesFromGlob(String pattern) |
55 | { | |
56 | 164 | return getFilesFromGlob(pattern, true); |
57 | } | |
58 | ||
59 | 182 | public static List<File> getFilesFromGlob(String pattern, |
60 | boolean allowSingleFilenameThatDoesNotExist) | |
61 | { | |
62 | 182 | pattern = substituteHomeDir(pattern); |
63 | 182 | String relativePattern = pattern.startsWith(File.separator) ? null |
64 | : pattern; | |
65 | 182 | List<File> files = new ArrayList<>(); |
66 | /* | |
67 | * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing. | |
68 | * We look for the first glob character * { ? and then look for the last File.separator before that. | |
69 | * Then we can reset the path to look at and shorten the globbing pattern. | |
70 | * Relative paths can be used in pattern, which work from the pwd (though these are converted into | |
71 | * full paths in the match). | |
72 | */ | |
73 | 182 | int firstGlobChar = -1; |
74 | 182 | boolean foundGlobChar = false; |
75 | 182 | for (char c : new char[] { '*', '{', '?' }) |
76 | { | |
77 | 546 | if (pattern.indexOf(c) > -1 |
78 | && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar)) | |
79 | { | |
80 | 65 | firstGlobChar = pattern.indexOf(c); |
81 | 65 | foundGlobChar = true; |
82 | } | |
83 | } | |
84 | 182 | int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar); |
85 | 182 | if (foundGlobChar) |
86 | { | |
87 | 65 | String pS = pattern.substring(0, lastFS + 1); |
88 | 65 | String rest = pattern.substring(lastFS + 1); |
89 | 65 | if ("".equals(pS)) |
90 | { | |
91 | 0 | pS = "."; |
92 | } | |
93 | 65 | Path parentDir = Paths.get(pS); |
94 | 65 | if (parentDir.toFile().exists()) |
95 | { | |
96 | 65 | try |
97 | { | |
98 | 65 | String glob = "glob:" + parentDir.toString() + File.separator |
99 | + rest; | |
100 | 65 | if (Platform.isWin()) |
101 | { | |
102 | // escape "\\" on Windows | |
103 | // This ultimately replaces "\\" == '\' with "\\\\" = '\\' to escape | |
104 | // backslashes | |
105 | 0 | glob = glob.replaceAll("\\\\", "\\\\\\\\"); |
106 | } | |
107 | 65 | PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob); |
108 | 65 | int maxDepth = rest.contains("**") ? 1028 |
109 | : (int) (rest.chars() | |
110 | .filter(ch -> ch == File.separatorChar).count()) | |
111 | + 1; | |
112 | ||
113 | 65 | Files.walkFileTree(parentDir, |
114 | EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth, | |
115 | new SimpleFileVisitor<Path>() | |
116 | { | |
117 | 6437 | @Override |
118 | public FileVisitResult visitFile(Path path, | |
119 | BasicFileAttributes attrs) throws IOException | |
120 | { | |
121 | 6437 | if (pm.matches(path)) |
122 | { | |
123 | 3172 | files.add(path.toFile()); |
124 | } | |
125 | 6437 | return FileVisitResult.CONTINUE; |
126 | } | |
127 | ||
128 | 0 | @Override |
129 | public FileVisitResult visitFileFailed(Path file, | |
130 | IOException exc) throws IOException | |
131 | { | |
132 | 0 | return FileVisitResult.CONTINUE; |
133 | } | |
134 | }); | |
135 | } catch (IOException e) | |
136 | { | |
137 | 0 | e.printStackTrace(); |
138 | } | |
139 | } | |
140 | } | |
141 | else | |
142 | { | |
143 | // no wildcards | |
144 | 117 | File f = new File(pattern); |
145 | 117 | if (allowSingleFilenameThatDoesNotExist || f.exists()) |
146 | { | |
147 | 109 | files.add(f); |
148 | } | |
149 | } | |
150 | 182 | Collections.sort(files); |
151 | ||
152 | 182 | return files; |
153 | } | |
154 | ||
155 | 139 | public static List<String> getFilenamesFromGlob(String pattern) |
156 | { | |
157 | // convert list of Files to list of File.getPath() Strings | |
158 | 139 | return getFilesFromGlob(pattern).stream().map(f -> f.getPath()) |
159 | .collect(Collectors.toList()); | |
160 | } | |
161 | ||
162 | 373 | public static String substituteHomeDir(String path) |
163 | { | |
164 | 373 | return path.startsWith("~" + File.separator) |
165 | ? System.getProperty("user.home") + path.substring(1) | |
166 | : path; | |
167 | } | |
168 | ||
169 | /* | |
170 | * This method returns the basename of File file | |
171 | */ | |
172 | 78 | public static String getBasename(File file) |
173 | { | |
174 | 78 | return getBasenameOrExtension(file, false); |
175 | } | |
176 | ||
177 | /* | |
178 | * This method returns the extension of File file. | |
179 | */ | |
180 | 10 | public static String getExtension(File file) |
181 | { | |
182 | 10 | return getBasenameOrExtension(file, true); |
183 | } | |
184 | ||
185 | 88 | public static String getBasenameOrExtension(File file, boolean extension) |
186 | { | |
187 | 88 | if (file == null) |
188 | 0 | return null; |
189 | ||
190 | 88 | String value = null; |
191 | 88 | String filename = file.getName(); |
192 | 88 | int lastDot = filename.lastIndexOf('.'); |
193 | 88 | if (lastDot > 0) // don't truncate if starts with '.' |
194 | { | |
195 | 87 | value = extension ? filename.substring(lastDot + 1) |
196 | : filename.substring(0, lastDot); | |
197 | } | |
198 | else | |
199 | { | |
200 | 1 | value = extension ? "" : filename; |
201 | } | |
202 | 88 | return value; |
203 | } | |
204 | ||
205 | /* | |
206 | * This method returns the dirname of the first --append or --open value. | |
207 | * Used primarily for substitutions in output filenames. | |
208 | */ | |
209 | 104 | public static String getDirname(File file) |
210 | { | |
211 | 104 | if (file == null) |
212 | 0 | return null; |
213 | ||
214 | 104 | String dirname = null; |
215 | 104 | File p = file.getParentFile(); |
216 | 104 | if (p == null) |
217 | { | |
218 | 0 | p = new File("."); |
219 | } | |
220 | 104 | File d = new File(substituteHomeDir(p.getPath())); |
221 | 104 | dirname = d.getPath(); |
222 | 104 | return dirname; |
223 | } | |
224 | ||
225 | 19 | public static String convertWildcardsToPath(String value, String wildcard, |
226 | String dirname, String basename) | |
227 | { | |
228 | 19 | if (value == null) |
229 | { | |
230 | 0 | return null; |
231 | } | |
232 | 19 | StringBuilder path = new StringBuilder(); |
233 | 19 | int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar); |
234 | 19 | int wildcardBeforeIndex = value.indexOf(wildcard); |
235 | 19 | if (lastFileSeparatorIndex > wildcard.length() - 1 |
236 | && wildcardBeforeIndex < lastFileSeparatorIndex) | |
237 | { | |
238 | 12 | path.append(value.substring(0, wildcardBeforeIndex)); |
239 | 12 | path.append(dirname); |
240 | 12 | path.append(value.substring(wildcardBeforeIndex + wildcard.length(), |
241 | lastFileSeparatorIndex + 1)); | |
242 | } | |
243 | else | |
244 | { | |
245 | 7 | path.append(value.substring(0, lastFileSeparatorIndex + 1)); |
246 | } | |
247 | 19 | int wildcardAfterIndex = value.indexOf(wildcard, |
248 | lastFileSeparatorIndex); | |
249 | 19 | if (wildcardAfterIndex > lastFileSeparatorIndex) |
250 | { | |
251 | 14 | path.append(value.substring(lastFileSeparatorIndex + 1, |
252 | wildcardAfterIndex)); | |
253 | 14 | path.append(basename); |
254 | 14 | path.append(value.substring(wildcardAfterIndex + wildcard.length())); |
255 | } | |
256 | else | |
257 | { | |
258 | 5 | path.append(value.substring(lastFileSeparatorIndex + 1)); |
259 | } | |
260 | 19 | return path.toString(); |
261 | } | |
262 | ||
263 | 114 | public static File getParentDir(File file) |
264 | { | |
265 | 114 | if (file == null) |
266 | { | |
267 | 0 | return null; |
268 | } | |
269 | 114 | File parentDir = file.getAbsoluteFile().getParentFile(); |
270 | 114 | return parentDir; |
271 | } | |
272 | ||
273 | 114 | public static boolean checkParentDir(File file, boolean mkdirs) |
274 | { | |
275 | 114 | if (file == null) |
276 | { | |
277 | 0 | return false; |
278 | } | |
279 | 114 | File parentDir = getParentDir(file); |
280 | 114 | if (parentDir.exists()) |
281 | { | |
282 | // already exists, nothing to do so nothing to worry about! | |
283 | 114 | return true; |
284 | } | |
285 | ||
286 | 0 | if (!mkdirs) |
287 | { | |
288 | 0 | return false; |
289 | } | |
290 | ||
291 | 0 | Path path = file.toPath(); |
292 | 0 | for (int i = 0; i < path.getNameCount(); i++) |
293 | { | |
294 | 0 | Path p = path.getName(i); |
295 | 0 | if ("..".equals(p.toString())) |
296 | { | |
297 | 0 | LaunchUtils.syserr(true, false, |
298 | "Cautiously not running mkdirs on " + file.toString() | |
299 | + " because the path to be made contains '..'"); | |
300 | 0 | return false; |
301 | } | |
302 | } | |
303 | ||
304 | 0 | return mkdirs(parentDir); |
305 | } | |
306 | ||
307 | /** | |
308 | * get a guessed file extension from a String only | |
309 | * | |
310 | * @param String | |
311 | * filename | |
312 | * @return String extension | |
313 | */ | |
314 | 15 | public static String getExtension(String filename) |
315 | { | |
316 | 15 | return getBaseOrExtension(filename, true); |
317 | } | |
318 | ||
319 | /** | |
320 | * getBase returns everything in a path/URI up to (and including) an extension | |
321 | * dot. Note this is not the same as getBasename() since getBasename() only | |
322 | * gives the filename base, not the path too. If no extension dot is found | |
323 | * (i.e. a dot in character position 2 or more of the filename (after the last | |
324 | * slash) then the whole path is considered the base. | |
325 | * | |
326 | * @param filename | |
327 | * @return String base | |
328 | */ | |
329 | 15 | public static String getBase(String filename) |
330 | { | |
331 | 15 | return getBaseOrExtension(filename, false); |
332 | } | |
333 | ||
334 | 30 | public static String getBaseOrExtension(String filename0, |
335 | boolean extension) | |
336 | { | |
337 | 30 | if (filename0 == null) |
338 | { | |
339 | 0 | return null; |
340 | } | |
341 | 30 | String filename = filename0; |
342 | 30 | boolean isUrl = false; |
343 | 30 | if (HttpUtils.startsWithHttpOrHttps(filename)) |
344 | { | |
345 | 10 | try |
346 | { | |
347 | 10 | URL url = new URL(filename); |
348 | 10 | filename = url.getPath(); |
349 | 10 | isUrl = true; |
350 | } catch (MalformedURLException e) | |
351 | { | |
352 | // continue to treat as a filename | |
353 | } | |
354 | } | |
355 | 30 | int dot = filename.lastIndexOf('.'); |
356 | 30 | int slash = filename.lastIndexOf('/'); |
357 | 30 | if (!File.separator.equals("/") && !isUrl) |
358 | { | |
359 | 0 | slash = filename.lastIndexOf(File.separator); |
360 | } | |
361 | // only the dot of the filename (not dots in path) and not if it's a .hidden | |
362 | // file | |
363 | 30 | boolean hasExtension = dot > slash + 1; |
364 | 30 | if (extension) |
365 | { | |
366 | 15 | return hasExtension ? filename.substring(dot + 1) : null; |
367 | } | |
368 | else | |
369 | { | |
370 | 15 | dot = filename0.lastIndexOf('.'); |
371 | 15 | return hasExtension ? filename0.substring(0, dot + 1) : filename0; |
372 | } | |
373 | } | |
374 | ||
375 | 0 | public static Path getCanonicalPath(Path path) |
376 | { | |
377 | 0 | return path.normalize(); |
378 | } | |
379 | ||
380 | 0 | public static Path getCanonicalPath(File file) |
381 | { | |
382 | 0 | return getCanonicalPath(file.toPath()); |
383 | } | |
384 | ||
385 | 0 | public static Path getCanonicalPath(String pathString) |
386 | { | |
387 | 0 | return getCanonicalPath(Paths.get(pathString)); |
388 | } | |
389 | ||
390 | 0 | public static File getCanonicalFile(File file) |
391 | { | |
392 | 0 | return getCanonicalPath(file).toFile(); |
393 | } | |
394 | ||
395 | 0 | public static File getCanonicalFile(String pathString) |
396 | { | |
397 | 0 | return getCanonicalPath(Paths.get(pathString)).toFile(); |
398 | } | |
399 | ||
400 | 0 | public static boolean mkdirs(File file) |
401 | { | |
402 | 0 | try |
403 | { | |
404 | 0 | Files.createDirectories(getCanonicalPath(file)); |
405 | 0 | return file.exists(); |
406 | } catch (IOException e) | |
407 | { | |
408 | 0 | LaunchUtils.syserr(true, false, "Failed to make directory " + file |
409 | + "\n" + e.getStackTrace()); | |
410 | } | |
411 | 0 | return false; |
412 | } | |
413 | /** | |
414 | * Look for files that use a template, with two "%s" formatting entries, to | |
415 | * look for files with the template substituted with combinations of root and | |
416 | * version. If versionWhitelist is not null then these paths will be added. If | |
417 | * versionBlacklist is not null then globbed versions will be looked for and | |
418 | * these versions excluded. If both are given then both will be included. If | |
419 | * separator is not null then it will be added before the version number, and | |
420 | * additionally a path without the separator and version will be looked for or | |
421 | * added if the whitelist or blacklist are supplied respectively. | |
422 | * | |
423 | * @param templates | |
424 | * @param roots | |
425 | * @param versionWhitelist | |
426 | * @param versionBlacklist | |
427 | * @param separator | |
428 | * @return | |
429 | */ | |
430 | 7 | public static List<File> getMatchingVersionedFiles(String[] templates, |
431 | String[] roots, String[] versionWhitelist, | |
432 | String[] versionBlacklist, String versionSeparator, | |
433 | boolean exists) | |
434 | { | |
435 | 7 | Set<File> matchingFiles = new HashSet<>(); |
436 | 7 | if (templates == null) |
437 | { | |
438 | 0 | Console.debug( |
439 | "getMatchingVersionedFiles called with a null template array"); | |
440 | 0 | List<File> files = new ArrayList<File>(); |
441 | 0 | files.addAll(matchingFiles); |
442 | 0 | return files; |
443 | } | |
444 | ||
445 | 7 | for (String template : templates) |
446 | { | |
447 | 7 | Console.debug("Using template '" + template + "'"); |
448 | 7 | for (String root : roots) |
449 | { | |
450 | 14 | Console.debug("Using root '" + root + "'"); |
451 | ||
452 | // Blacklist. Use a file glob for version and see what's there | |
453 | 14 | if (versionBlacklist != null) |
454 | { | |
455 | 10 | String globMatch = String.format(template, root, "*"); |
456 | 10 | Console.debug("Using glob '" + globMatch + "'"); |
457 | 10 | List<File> foundFiles = FileUtils.getFilesFromGlob(globMatch, |
458 | false); | |
459 | 10 | for (File found : foundFiles) |
460 | { | |
461 | 15 | Console.debug("Checking " + found.getPath() + " is okay"); |
462 | 15 | boolean add = true; |
463 | 15 | for (String notVersion : versionBlacklist) |
464 | { | |
465 | 20 | StringBuilder vSB = new StringBuilder(); |
466 | 20 | if (versionSeparator != null) |
467 | { | |
468 | 15 | vSB.append(versionSeparator); |
469 | } | |
470 | 20 | vSB.append(notVersion); |
471 | 20 | String versionString = vSB.toString(); |
472 | 20 | if (String.format(template, root, versionString) |
473 | .equals(found.getPath())) | |
474 | { | |
475 | 8 | add = false; |
476 | 8 | Console.debug( |
477 | "Not adding " + found.getPath() + ": version '" | |
478 | + notVersion + "' is in the blacklist"); | |
479 | 8 | break; |
480 | } | |
481 | } | |
482 | 15 | if (add) |
483 | { | |
484 | 7 | Console.debug("Adding " + found.getPath() + " to list"); |
485 | 7 | matchingFiles.add(found); |
486 | } | |
487 | } | |
488 | ||
489 | 10 | if (versionSeparator != null) |
490 | { | |
491 | // try without a version number too | |
492 | 8 | String nonVersioned = String.format(template, root, ""); |
493 | 8 | matchingFiles.addAll( |
494 | FileUtils.getFilesFromGlob(nonVersioned, false)); | |
495 | } | |
496 | } | |
497 | ||
498 | // Whitelist. Just add a path for every whitelisted version (or check it | |
499 | // exists). | |
500 | 14 | if (versionWhitelist != null) |
501 | { | |
502 | 10 | Console.debug("Adding " + versionWhitelist.length |
503 | + " whitelist versions"); | |
504 | 10 | for (String addVersion : versionWhitelist) |
505 | { | |
506 | 10 | StringBuilder vSB = new StringBuilder(); |
507 | 10 | if (versionSeparator != null) |
508 | { | |
509 | 8 | vSB.append(versionSeparator); |
510 | } | |
511 | 10 | vSB.append(addVersion); |
512 | 10 | String versionString = vSB.toString(); |
513 | 10 | String versionPath = String.format(template, root, |
514 | versionString); | |
515 | 10 | Console.debug("Adding whitelist path '" + versionPath + "'"); |
516 | 10 | File file = new File(versionPath); |
517 | 10 | if (file.exists() || !exists) |
518 | { | |
519 | 6 | matchingFiles.add(file); |
520 | } | |
521 | } | |
522 | ||
523 | 10 | if (versionSeparator != null) |
524 | { | |
525 | // try without a version number too | |
526 | 8 | String nonVersioned = String.format(template, root, ""); |
527 | 8 | File file = new File(nonVersioned); |
528 | 8 | if (file.exists() || !exists) |
529 | { | |
530 | 2 | matchingFiles.add(file); |
531 | } | |
532 | } | |
533 | } | |
534 | } | |
535 | } | |
536 | ||
537 | 7 | List<File> files = new ArrayList<File>(); |
538 | 7 | files.addAll(matchingFiles); |
539 | 7 | return files; |
540 | } | |
541 | } |