Clover icon

Coverage Report

  1. Project Clover database Tue Nov 4 2025 11:21:43 GMT
  2. Package jalview.util

File FileUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart9.png
13% of files have more coverage

Code metrics

84
176
23
1
542
414
75
0.43
7.65
23
3.26

Classes

Class Line # Actions
FileUtils 44 176 75
0.8480565584.8%
 

Contributing tests

This file is covered by 161 tests. .

Source view

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    public class FileUtils
45    {
46    /*
47    * Given string glob pattern (see
48    * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
49    * ) return a List of Files that match the pattern.
50    * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities.
51    */
 
52  175 toggle public static List<File> getFilesFromGlob(String pattern)
53    {
54  175 return getFilesFromGlob(pattern, true);
55    }
56   
 
57  193 toggle public static List<File> getFilesFromGlob(String pattern,
58    boolean allowSingleFilenameThatDoesNotExist)
59    {
60  193 pattern = substituteHomeDir(pattern);
61  193 String relativePattern = pattern.startsWith(File.separator) ? null
62    : pattern;
63  193 List<File> files = new ArrayList<>();
64    /*
65    * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
66    * We look for the first glob character * { ? and then look for the last File.separator before that.
67    * Then we can reset the path to look at and shorten the globbing pattern.
68    * Relative paths can be used in pattern, which work from the pwd (though these are converted into
69    * full paths in the match).
70    */
71  193 int firstGlobChar = -1;
72  193 boolean foundGlobChar = false;
73  193 for (char c : new char[] { '*', '{', '?' })
74    {
75  579 if (pattern.indexOf(c) > -1
76    && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
77    {
78  65 firstGlobChar = pattern.indexOf(c);
79  65 foundGlobChar = true;
80    }
81    }
82  193 int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
83  193 if (foundGlobChar)
84    {
85  65 String pS = pattern.substring(0, lastFS + 1);
86  65 String rest = pattern.substring(lastFS + 1);
87  65 if ("".equals(pS))
88    {
89  0 pS = ".";
90    }
91  65 Path parentDir = Paths.get(pS);
92  65 if (parentDir.toFile().exists())
93    {
94  65 try
95    {
96  65 String glob = "glob:" + parentDir.toString() + File.separator
97    + rest;
98    // don't use Platform.isWin() as this class is used in getdown
99  65 if (System.getProperty("os.name").indexOf("Win") >= 0)
100    {
101    // escape "\\" on Windows
102    // This ultimately replaces "\\" == '\' with "\\\\" = '\\' to escape
103    // backslashes
104  0 glob = glob.replaceAll("\\\\", "\\\\\\\\");
105    }
106  65 PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
107  65 int maxDepth = rest.contains("**") ? 1028
108    : (int) (rest.chars()
109    .filter(ch -> ch == File.separatorChar).count())
110    + 1;
111   
112  65 Files.walkFileTree(parentDir,
113    EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
114    new SimpleFileVisitor<Path>()
115    {
 
116  6475 toggle @Override
117    public FileVisitResult visitFile(Path path,
118    BasicFileAttributes attrs) throws IOException
119    {
120  6475 if (pm.matches(path))
121    {
122  3185 files.add(path.toFile());
123    }
124  6475 return FileVisitResult.CONTINUE;
125    }
126   
 
127  0 toggle @Override
128    public FileVisitResult visitFileFailed(Path file,
129    IOException exc) throws IOException
130    {
131  0 return FileVisitResult.CONTINUE;
132    }
133    });
134    } catch (IOException e)
135    {
136  0 e.printStackTrace();
137    }
138    }
139    }
140    else
141    {
142    // no wildcards
143  128 File f = new File(pattern);
144  128 if (allowSingleFilenameThatDoesNotExist || f.exists())
145    {
146  120 files.add(f);
147    }
148    }
149  193 Collections.sort(files);
150   
151  193 return files;
152    }
153   
 
154  150 toggle public static List<String> getFilenamesFromGlob(String pattern)
155    {
156    // convert list of Files to list of File.getPath() Strings
157  150 return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
158    .collect(Collectors.toList());
159    }
160   
 
161  385 toggle public static String substituteHomeDir(String path)
162    {
163  385 return path.startsWith("~" + File.separator)
164    ? System.getProperty("user.home") + path.substring(1)
165    : path;
166    }
167   
168    /*
169    * This method returns the basename of File file
170    */
 
171  78 toggle public static String getBasename(File file)
172    {
173  78 return getBasenameOrExtension(file, false);
174    }
175   
176    /*
177    * This method returns the extension of File file.
178    */
 
179  10 toggle public static String getExtension(File file)
180    {
181  10 return getBasenameOrExtension(file, true);
182    }
183   
 
184  88 toggle public static String getBasenameOrExtension(File file, boolean extension)
185    {
186  88 if (file == null)
187  0 return null;
188   
189  88 String value = null;
190  88 String filename = file.getName();
191  88 int lastDot = filename.lastIndexOf('.');
192  88 if (lastDot > 0) // don't truncate if starts with '.'
193    {
194  87 value = extension ? filename.substring(lastDot + 1)
195    : filename.substring(0, lastDot);
196    }
197    else
198    {
199  1 value = extension ? "" : filename;
200    }
201  88 return value;
202    }
203   
204    /*
205    * This method returns the dirname of the first --append or --open value.
206    * Used primarily for substitutions in output filenames.
207    */
 
208  104 toggle public static String getDirname(File file)
209    {
210  104 if (file == null)
211  0 return null;
212   
213  104 String dirname = null;
214  104 File p = file.getParentFile();
215  104 if (p == null)
216    {
217  0 p = new File(".");
218    }
219  104 File d = new File(substituteHomeDir(p.getPath()));
220  104 dirname = d.getPath();
221  104 return dirname;
222    }
223   
 
224  19 toggle public static String convertWildcardsToPath(String value, String wildcard,
225    String dirname, String basename)
226    {
227  19 if (value == null)
228    {
229  0 return null;
230    }
231  19 StringBuilder path = new StringBuilder();
232  19 int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
233  19 int wildcardBeforeIndex = value.indexOf(wildcard);
234  19 if (lastFileSeparatorIndex > wildcard.length() - 1
235    && wildcardBeforeIndex < lastFileSeparatorIndex)
236    {
237  12 path.append(value.substring(0, wildcardBeforeIndex));
238  12 path.append(dirname);
239  12 path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
240    lastFileSeparatorIndex + 1));
241    }
242    else
243    {
244  7 path.append(value.substring(0, lastFileSeparatorIndex + 1));
245    }
246  19 int wildcardAfterIndex = value.indexOf(wildcard,
247    lastFileSeparatorIndex);
248  19 if (wildcardAfterIndex > lastFileSeparatorIndex)
249    {
250  14 path.append(value.substring(lastFileSeparatorIndex + 1,
251    wildcardAfterIndex));
252  14 path.append(basename);
253  14 path.append(value.substring(wildcardAfterIndex + wildcard.length()));
254    }
255    else
256    {
257  5 path.append(value.substring(lastFileSeparatorIndex + 1));
258    }
259  19 return path.toString();
260    }
261   
 
262  114 toggle public static File getParentDir(File file)
263    {
264  114 if (file == null)
265    {
266  0 return null;
267    }
268  114 File parentDir = file.getAbsoluteFile().getParentFile();
269  114 return parentDir;
270    }
271   
 
272  114 toggle public static boolean checkParentDir(File file, boolean mkdirs)
273    {
274  114 if (file == null)
275    {
276  0 return false;
277    }
278  114 File parentDir = getParentDir(file);
279  114 if (parentDir.exists())
280    {
281    // already exists, nothing to do so nothing to worry about!
282  112 return true;
283    }
284   
285  2 if (!mkdirs)
286    {
287  0 return false;
288    }
289   
290  2 Path path = file.toPath();
291  20 for (int i = 0; i < path.getNameCount(); i++)
292    {
293  18 Path p = path.getName(i);
294  18 if ("..".equals(p.toString()))
295    {
296  0 LaunchUtils.syserr(true, false,
297    "Cautiously not running mkdirs on " + file.toString()
298    + " because the path to be made contains '..'");
299  0 return false;
300    }
301    }
302   
303  2 return mkdirs(parentDir);
304    }
305   
306    /**
307    * get a guessed file extension from a String only
308    *
309    * @param String
310    * filename
311    * @return String extension
312    */
 
313  15 toggle public static String getExtension(String filename)
314    {
315  15 return getBaseOrExtension(filename, true);
316    }
317   
318    /**
319    * getBase returns everything in a path/URI up to (and including) an extension
320    * dot. Note this is not the same as getBasename() since getBasename() only
321    * gives the filename base, not the path too. If no extension dot is found
322    * (i.e. a dot in character position 2 or more of the filename (after the last
323    * slash) then the whole path is considered the base.
324    *
325    * @param filename
326    * @return String base
327    */
 
328  15 toggle public static String getBase(String filename)
329    {
330  15 return getBaseOrExtension(filename, false);
331    }
332   
 
333  30 toggle public static String getBaseOrExtension(String filename0,
334    boolean extension)
335    {
336  30 if (filename0 == null)
337    {
338  0 return null;
339    }
340  30 String filename = filename0;
341  30 boolean isUrl = false;
342  30 if (HttpUtils.startsWithHttpOrHttps(filename))
343    {
344  10 try
345    {
346  10 URL url = new URL(filename);
347  10 filename = url.getPath();
348  10 isUrl = true;
349    } catch (MalformedURLException e)
350    {
351    // continue to treat as a filename
352    }
353    }
354  30 int dot = filename.lastIndexOf('.');
355  30 int slash = filename.lastIndexOf('/');
356  30 if (!File.separator.equals("/") && !isUrl)
357    {
358  0 slash = filename.lastIndexOf(File.separator);
359    }
360    // only the dot of the filename (not dots in path) and not if it's a .hidden
361    // file
362  30 boolean hasExtension = dot > slash + 1;
363  30 if (extension)
364    {
365  15 return hasExtension ? filename.substring(dot + 1) : null;
366    }
367    else
368    {
369  15 dot = filename0.lastIndexOf('.');
370  15 return hasExtension ? filename0.substring(0, dot + 1) : filename0;
371    }
372    }
373   
 
374  2 toggle public static Path getCanonicalPath(Path path)
375    {
376  2 return path.normalize();
377    }
378   
 
379  2 toggle public static Path getCanonicalPath(File file)
380    {
381  2 return getCanonicalPath(file.toPath());
382    }
383   
 
384  0 toggle public static Path getCanonicalPath(String pathString)
385    {
386  0 return getCanonicalPath(Paths.get(pathString));
387    }
388   
 
389  0 toggle public static File getCanonicalFile(File file)
390    {
391  0 return getCanonicalPath(file).toFile();
392    }
393   
 
394  0 toggle public static File getCanonicalFile(String pathString)
395    {
396  0 return getCanonicalPath(Paths.get(pathString)).toFile();
397    }
398   
 
399  2 toggle public static boolean mkdirs(File file)
400    {
401  2 try
402    {
403  2 Files.createDirectories(getCanonicalPath(file));
404  2 return file.exists();
405    } catch (IOException e)
406    {
407  0 LaunchUtils.syserr(true, false, "Failed to make directory " + file
408    + "\n" + e.getStackTrace());
409    }
410  0 return false;
411    }
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 toggle 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 ErrorLog.errPrintln(
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 ErrorLog.errPrintln("Using template '" + template + "'");
448  7 for (String root : roots)
449    {
450  14 ErrorLog.errPrintln("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 ErrorLog.errPrintln("Using glob '" + globMatch + "'");
457  10 List<File> foundFiles = FileUtils.getFilesFromGlob(globMatch,
458    false);
459  10 for (File found : foundFiles)
460    {
461  15 ErrorLog.errPrintln("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 ErrorLog.errPrintln(
477    "Not adding " + found.getPath() + ": version '"
478    + notVersion + "' is in the blacklist");
479  8 break;
480    }
481    }
482  15 if (add)
483    {
484  7 ErrorLog.errPrintln("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 ErrorLog.errPrintln("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 ErrorLog.errPrintln(
516    "Adding whitelist path '" + versionPath + "'");
517  10 File file = new File(versionPath);
518  10 if (file.exists() || !exists)
519    {
520  6 matchingFiles.add(file);
521    }
522    }
523   
524  10 if (versionSeparator != null)
525    {
526    // try without a version number too
527  8 String nonVersioned = String.format(template, root, "");
528  8 File file = new File(nonVersioned);
529  8 if (file.exists() || !exists)
530    {
531  2 matchingFiles.add(file);
532    }
533    }
534    }
535    }
536    }
537   
538  7 List<File> files = new ArrayList<File>();
539  7 files.addAll(matchingFiles);
540  7 return files;
541    }
542    }