Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.util

File FileUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart7.png
30% of files have more coverage

Code metrics

94
213
29
1
735
505
92
0.43
7.34
29
3.17

Classes

Class Line # Actions
FileUtils 49 213 92
0.675595267.6%
 

Contributing tests

This file is covered by 296 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.DirectoryStream;
28    import java.nio.file.FileSystems;
29    import java.nio.file.FileVisitOption;
30    import java.nio.file.FileVisitResult;
31    import java.nio.file.Files;
32    import java.nio.file.Path;
33    import java.nio.file.PathMatcher;
34    import java.nio.file.Paths;
35    import java.nio.file.SimpleFileVisitor;
36    import java.nio.file.attribute.BasicFileAttributes;
37    import java.util.ArrayList;
38    import java.util.Arrays;
39    import java.util.Collections;
40    import java.util.EnumSet;
41    import java.util.HashSet;
42    import java.util.List;
43    import java.util.Set;
44    import java.util.stream.Collectors;
45   
46    /**
47    * Miscellaneous file-related functions
48    */
 
49    public final class FileUtils
50    {
51    /*
52    * Given string glob pattern (see
53    * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
54    * ) return a List of Files that match the pattern.
55    * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities.
56    */
 
57  162 toggle public static List<File> getFilesFromGlob(String pattern)
58    {
59  162 return getFilesFromGlob(pattern, true);
60    }
61   
 
62  180 toggle public static List<File> getFilesFromGlob(String pattern,
63    boolean allowSingleFilenameThatDoesNotExist)
64    {
65  180 pattern = substituteHomeDir(pattern);
66  180 String relativePattern = pattern.startsWith(File.separator) ? null
67    : pattern;
68  180 List<File> files = new ArrayList<>();
69    /*
70    * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
71    * We look for the first glob character * { ? and then look for the last File.separator before that.
72    * Then we can reset the path to look at and shorten the globbing pattern.
73    * Relative paths can be used in pattern, which work from the pwd (though these are converted into
74    * full paths in the match).
75    */
76  180 int firstGlobChar = -1;
77  180 boolean foundGlobChar = false;
78  180 for (char c : new char[] { '*', '{', '?' })
79    {
80  540 if (pattern.indexOf(c) > -1
81    && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
82    {
83  65 firstGlobChar = pattern.indexOf(c);
84  65 foundGlobChar = true;
85    }
86    }
87  180 int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
88  180 if (foundGlobChar)
89    {
90  65 String pS = pattern.substring(0, lastFS + 1);
91  65 String rest = pattern.substring(lastFS + 1);
92  65 if ("".equals(pS))
93    {
94  0 pS = ".";
95    }
96  65 Path parentDir = Paths.get(pS);
97  65 if (parentDir.toFile().exists())
98    {
99  65 try
100    {
101  65 String glob = "glob:" + parentDir.toString() + File.separator
102    + rest;
103    // don't use Platform.isWin() as this class is used in getdown
104  65 if (System.getProperty("os.name").indexOf("Win") >= 0)
105    {
106    // escape "\\" on Windows
107    // This ultimately replaces "\\" == '\' with "\\\\" = '\\' to escape
108    // backslashes
109  0 glob = glob.replaceAll("\\\\", "\\\\\\\\");
110    }
111  65 PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
112  65 int maxDepth = rest.contains("**") ? 1028
113    : (int) (rest.chars()
114    .filter(ch -> ch == File.separatorChar).count())
115    + 1;
116   
117  65 Files.walkFileTree(parentDir,
118    EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
119    new SimpleFileVisitor<Path>()
120    {
 
121  7126 toggle @Override
122    public FileVisitResult visitFile(Path path,
123    BasicFileAttributes attrs) throws IOException
124    {
125  7126 if (pm.matches(path))
126    {
127  3530 files.add(path.toFile());
128    }
129  7126 return FileVisitResult.CONTINUE;
130    }
131   
 
132  0 toggle @Override
133    public FileVisitResult visitFileFailed(Path file,
134    IOException exc) throws IOException
135    {
136  0 return FileVisitResult.CONTINUE;
137    }
138    });
139    } catch (IOException e)
140    {
141  0 e.printStackTrace();
142    }
143    }
144    }
145    else
146    {
147    // no wildcards
148  115 File f = new File(pattern);
149  115 if (allowSingleFilenameThatDoesNotExist || f.exists())
150    {
151  107 files.add(f);
152    }
153    }
154  180 Collections.sort(files);
155   
156  180 return files;
157    }
158   
 
159  137 toggle public static List<String> getFilenamesFromGlob(String pattern)
160    {
161    // convert list of Files to list of File.getPath() Strings
162  137 return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
163    .collect(Collectors.toList());
164    }
165   
 
166  374 toggle public static String substituteHomeDir(String path)
167    {
168  374 return path.startsWith("~" + File.separator)
169    ? System.getProperty("user.home") + path.substring(1)
170    : path;
171    }
172   
173    /*
174    * This method returns the basename of File file
175    */
 
176  78 toggle public static String getBasename(File file)
177    {
178  78 return getBasenameOrExtension(file, false);
179    }
180   
181    /*
182    * This method returns the extension of File file.
183    */
 
184  0 toggle public static String getExtension(File file)
185    {
186  0 return getBasenameOrExtension(file, true);
187    }
188   
 
189  78 toggle public static String getBasenameOrExtension(File file, boolean extension)
190    {
191  78 if (file == null)
192  0 return null;
193   
194  78 String value = null;
195  78 String filename = file.getName();
196  78 int lastDot = filename.lastIndexOf('.');
197  78 if (lastDot > 0) // don't truncate if starts with '.'
198    {
199  77 value = extension ? filename.substring(lastDot + 1)
200    : filename.substring(0, lastDot);
201    }
202    else
203    {
204  1 value = extension ? "" : filename;
205    }
206  78 return value;
207    }
208   
209    /*
210    * This method returns the dirname of the first --append or --open value.
211    * Used primarily for substitutions in output filenames.
212    */
 
213  104 toggle public static String getDirname(File file)
214    {
215  104 if (file == null)
216  0 return null;
217   
218  104 String dirname = null;
219  104 File p = file.getParentFile();
220  104 if (p == null)
221    {
222  0 p = new File(".");
223    }
224  104 File d = new File(substituteHomeDir(p.getPath()));
225  104 dirname = d.getPath();
226  104 return dirname;
227    }
228   
 
229  19 toggle public static String convertWildcardsToPath(String value, String wildcard,
230    String dirname, String basename)
231    {
232  19 if (value == null)
233    {
234  0 return null;
235    }
236  19 StringBuilder path = new StringBuilder();
237  19 int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
238  19 int wildcardBeforeIndex = value.indexOf(wildcard);
239  19 if (lastFileSeparatorIndex > wildcard.length() - 1
240    && wildcardBeforeIndex < lastFileSeparatorIndex)
241    {
242  12 path.append(value.substring(0, wildcardBeforeIndex));
243  12 path.append(dirname);
244  12 path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
245    lastFileSeparatorIndex + 1));
246    }
247    else
248    {
249  7 path.append(value.substring(0, lastFileSeparatorIndex + 1));
250    }
251  19 int wildcardAfterIndex = value.indexOf(wildcard,
252    lastFileSeparatorIndex);
253  19 if (wildcardAfterIndex > lastFileSeparatorIndex)
254    {
255  14 path.append(value.substring(lastFileSeparatorIndex + 1,
256    wildcardAfterIndex));
257  14 path.append(basename);
258  14 path.append(value.substring(wildcardAfterIndex + wildcard.length()));
259    }
260    else
261    {
262  5 path.append(value.substring(lastFileSeparatorIndex + 1));
263    }
264  19 return path.toString();
265    }
266   
 
267  97 toggle public static File getParentDir(File file)
268    {
269  97 if (file == null)
270    {
271  0 return null;
272    }
273  97 File parentDir = file.getAbsoluteFile().getParentFile();
274  97 return parentDir;
275    }
276   
 
277  97 toggle public static boolean checkParentDir(File file, boolean mkdirs)
278    {
279  97 if (file == null)
280    {
281  0 return false;
282    }
283  97 File parentDir = getParentDir(file);
284  97 if (parentDir.exists())
285    {
286    // already exists, nothing to do so nothing to worry about!
287  97 return true;
288    }
289   
290  0 if (!mkdirs)
291    {
292  0 return false;
293    }
294   
295  0 Path path = file.toPath();
296  0 for (int i = 0; i < path.getNameCount(); i++)
297    {
298  0 Path p = path.getName(i);
299  0 if ("..".equals(p.toString()))
300    {
301  0 LaunchUtils.syserr(true, false,
302    "Cautiously not running mkdirs on " + file.toString()
303    + " because the path to be made contains '..'");
304  0 return false;
305    }
306    }
307   
308  0 return mkdirs(parentDir);
309    }
310   
311    /**
312    * get a guessed file extension from a String only
313    *
314    * @param String
315    * filename
316    * @return String extension
317    */
 
318  15 toggle public static String getExtension(String filename)
319    {
320  15 return getBaseOrExtension(filename, true);
321    }
322   
323    /**
324    * getBase returns everything in a path/URI up to (and including) an extension
325    * dot. Note this is not the same as getBasename() since getBasename() only
326    * gives the filename base, not the path too. If no extension dot is found
327    * (i.e. a dot in character position 2 or more of the filename (after the last
328    * slash) then the whole path is considered the base.
329    *
330    * @param filename
331    * @return String base
332    */
 
333  15 toggle public static String getBase(String filename)
334    {
335  15 return getBaseOrExtension(filename, false);
336    }
337   
 
338  30 toggle public static String getBaseOrExtension(String filename0,
339    boolean extension)
340    {
341  30 if (filename0 == null)
342    {
343  0 return null;
344    }
345  30 String filename = filename0;
346  30 boolean isUrl = false;
347  30 if (HttpUtils.startsWithHttpOrHttps(filename))
348    {
349  10 try
350    {
351  10 URL url = new URL(filename);
352  10 filename = url.getPath();
353  10 isUrl = true;
354    } catch (MalformedURLException e)
355    {
356    // continue to treat as a filename
357    }
358    }
359  30 int dot = filename.lastIndexOf('.');
360  30 int slash = filename.lastIndexOf('/');
361  30 if (!File.separator.equals("/") && !isUrl)
362    {
363  0 slash = filename.lastIndexOf(File.separator);
364    }
365    // only the dot of the filename (not dots in path) and not if it's a .hidden
366    // file
367  30 boolean hasExtension = dot > slash + 1;
368  30 if (extension)
369    {
370  15 return hasExtension ? filename.substring(dot + 1) : null;
371    }
372    else
373    {
374  15 dot = filename0.lastIndexOf('.');
375  15 return hasExtension ? filename0.substring(0, dot + 1) : filename0;
376    }
377    }
378   
 
379  0 toggle public static Path getCanonicalPath(Path path)
380    {
381  0 return path.normalize();
382    }
383   
 
384  0 toggle public static Path getCanonicalPath(File file)
385    {
386  0 return getCanonicalPath(file.toPath());
387    }
388   
 
389  0 toggle public static Path getCanonicalPath(String pathString)
390    {
391  0 return getCanonicalPath(Paths.get(pathString));
392    }
393   
 
394  0 toggle public static File getCanonicalFile(File file)
395    {
396  0 return getCanonicalPath(file).toFile();
397    }
398   
 
399  0 toggle public static File getCanonicalFile(String pathString)
400    {
401  0 return getCanonicalPath(Paths.get(pathString)).toFile();
402    }
403   
 
404  0 toggle public static boolean mkdirs(File file)
405    {
406  0 try
407    {
408  0 Files.createDirectories(getCanonicalPath(file));
409  0 return file.exists();
410    } catch (IOException e)
411    {
412  0 LaunchUtils.syserr(true, false, "Failed to make directory " + file
413    + "\n" + e.getStackTrace());
414    }
415  0 return false;
416    }
417   
418    /**
419    * Look for files that use a template, with two "%s" formatting entries, to
420    * look for files with the template substituted with combinations of root and
421    * version. If versionWhitelist is not null then these paths will be added. If
422    * versionBlacklist is not null then globbed versions will be looked for and
423    * these versions excluded. If both are given then both will be included. If
424    * separator is not null then it will be added before the version number, and
425    * additionally a path without the separator and version will be looked for or
426    * added if the whitelist or blacklist are supplied respectively.
427    *
428    * @param templates
429    * @param roots
430    * @param versionWhitelist
431    * @param versionBlacklist
432    * @param separator
433    * @return
434    */
 
435  7 toggle public static List<File> getMatchingVersionedFiles(String[] templates,
436    String[] roots, String[] versionWhitelist,
437    String[] versionBlacklist, String versionSeparator,
438    boolean exists)
439    {
440  7 Set<File> matchingFiles = new HashSet<>();
441  7 if (templates == null)
442    {
443  0 ErrorLog.errPrintln(
444    "getMatchingVersionedFiles called with a null template array");
445  0 List<File> files = new ArrayList<File>();
446  0 files.addAll(matchingFiles);
447  0 return files;
448    }
449   
450  7 for (String template : templates)
451    {
452  7 ErrorLog.errPrintln("Using template '" + template + "'");
453  7 for (String root : roots)
454    {
455  14 ErrorLog.errPrintln("Using root '" + root + "'");
456   
457    // Blacklist. Use a file glob for version and see what's there
458  14 if (versionBlacklist != null)
459    {
460  10 String globMatch = String.format(template, root, "*");
461  10 ErrorLog.errPrintln("Using glob '" + globMatch + "'");
462  10 List<File> foundFiles = FileUtils.getFilesFromGlob(globMatch,
463    false);
464  10 for (File found : foundFiles)
465    {
466  15 ErrorLog.errPrintln("Checking " + found.getPath() + " is okay");
467  15 boolean add = true;
468  15 for (String notVersion : versionBlacklist)
469    {
470  20 StringBuilder vSB = new StringBuilder();
471  20 if (versionSeparator != null)
472    {
473  15 vSB.append(versionSeparator);
474    }
475  20 vSB.append(notVersion);
476  20 String versionString = vSB.toString();
477  20 if (String.format(template, root, versionString)
478    .equals(found.getPath()))
479    {
480  8 add = false;
481  8 ErrorLog.errPrintln(
482    "Not adding " + found.getPath() + ": version '"
483    + notVersion + "' is in the blacklist");
484  8 break;
485    }
486    }
487  15 if (add)
488    {
489  7 ErrorLog.errPrintln("Adding " + found.getPath() + " to list");
490  7 matchingFiles.add(found);
491    }
492    }
493   
494  10 if (versionSeparator != null)
495    {
496    // try without a version number too
497  8 String nonVersioned = String.format(template, root, "");
498  8 matchingFiles.addAll(
499    FileUtils.getFilesFromGlob(nonVersioned, false));
500    }
501    }
502   
503    // Whitelist. Just add a path for every whitelisted version (or check it
504    // exists).
505  14 if (versionWhitelist != null)
506    {
507  10 ErrorLog.errPrintln("Adding " + versionWhitelist.length
508    + " whitelist versions");
509  10 for (String addVersion : versionWhitelist)
510    {
511  10 StringBuilder vSB = new StringBuilder();
512  10 if (versionSeparator != null)
513    {
514  8 vSB.append(versionSeparator);
515    }
516  10 vSB.append(addVersion);
517  10 String versionString = vSB.toString();
518  10 String versionPath = String.format(template, root,
519    versionString);
520  10 ErrorLog.errPrintln(
521    "Adding whitelist path '" + versionPath + "'");
522  10 File file = new File(versionPath);
523  10 if (file.exists() || !exists)
524    {
525  6 matchingFiles.add(file);
526    }
527    }
528   
529  10 if (versionSeparator != null)
530    {
531    // try without a version number too
532  8 String nonVersioned = String.format(template, root, "");
533  8 File file = new File(nonVersioned);
534  8 if (file.exists() || !exists)
535    {
536  2 matchingFiles.add(file);
537    }
538    }
539    }
540    }
541    }
542   
543  7 List<File> files = new ArrayList<File>();
544  7 files.addAll(matchingFiles);
545  7 return files;
546    }
547   
548    /**
549    * Answers the executable file for the given command, or null if not found or
550    * not executable. The path to the executable is the command name prefixed by
551    * the given folder path, optionally with .exe appended.
552    *
553    * @param cmd
554    * command short name, for example hmmbuild
555    * @param binaryPath
556    * parent folder for the executable
557    * @return
558    */
 
559  487 toggle public static File getExecutable(String cmd, String binaryPath)
560    {
561  487 File file = new File(binaryPath, cmd);
562  487 if (!file.canExecute())
563    {
564  487 file = new File(binaryPath, cmd + ".exe");
565    {
566  487 if (!file.canExecute())
567    {
568  487 file = null;
569    }
570    }
571    }
572  487 return file;
573    }
574   
575    /**
576    * Answers the path to the folder containing the given executable file, by
577    * searching the PATH environment variable. Answers null if no such executable
578    * can be found.
579    *
580    * @param cmd
581    * @return
582    */
 
583  0 toggle public static String getPathTo(String cmd)
584    {
585  0 String paths = System.getenv("PATH");
586    // backslash is to escape regular expression argument
587  0 for (String path : paths.split("\\" + File.pathSeparator))
588    {
589  0 if (getExecutable(cmd, path) != null)
590    {
591  0 return path;
592    }
593    }
594  0 return null;
595    }
596   
597    /**
598    * A convenience method to create a temporary file that is deleted on exit of
599    * the JVM
600    *
601    * @param prefix
602    * @param suffix
603    * @return
604    * @throws IOException
605    */
 
606  0 toggle public static File createTempFile(String prefix, String suffix)
607    throws IOException
608    {
609  0 File f = File.createTempFile(prefix, suffix);
610  0 f.deleteOnExit();
611  0 return f;
612    }
613   
614    /**
615    * Answers a (possibly empty) list of file paths found by searching below
616    * <code>from</code> that match the supplied regular expression
617    * <code>pattern</code>. Results may include <code>from</code> itself if it
618    * matches. If an exception occurs it is written to syserr and any results up to
619    * that point are returned. Note that the regular expression match applies to
620    * the whole path of any matched file.
621    * <p>
622    * WARNING: because the whole directory tree below <code>from</code> is
623    * searched, this method may be slow if used for a high level directory, or may
624    * exit prematurely if security or other exceptions occur.
625    *
626    * <pre>
627    * Example:
628    * findMatchingPaths(Paths.get("C:/Program Files"), ".*&#47chimera.exe$")
629    * </pre>
630    *
631    * @param from
632    * @param pattern
633    *
634    * @return
635    * @see https://stackoverflow.com/questions/794381/how-to-find-files-that-match-a-wildcard-string-in-java/31685610#comment62441832_31685610
636    */
 
637  0 toggle public static List<String> findMatchingPaths(Path from, String pattern)
638    {
639  0 List<String> matches = new ArrayList<>();
640  0 PathMatcher pathMatcher = FileSystems.getDefault()
641    .getPathMatcher("regex:" + pattern);
642  0 try
643    {
644  0 Files.walk(from).filter(pathMatcher::matches)
645    .forEach(m -> matches.add(m.toString()));
646    } catch (IOException e)
647    {
648  0 System.err.println(
649    "Error searching for " + pattern + " : " + e.toString());
650    }
651   
652  0 return matches;
653    }
654   
655    /**
656    * Answers a (possibly empty) list of paths to files below the given root path,
657    * that match the given pattern. The pattern should be a '/' delimited set of
658    * glob patterns, each of which is used to match child file names (not full
659    * paths). Note that 'directory spanning' glob patterns (**) are <em>not</em>
660    * supported by this method.
661    * <p>
662    * For example
663    *
664    * <pre>
665    * findMatches("C:\\", "Program Files*&#47Chimera*&#47bin/{chimera,chimera.exe}"
666    * </pre>
667    *
668    * would match "C:\Program Files\Chimera 1.11\bin\chimera.exe" and "C:\Program
669    * Files (x86)\Chimera 1.10.1\bin\chimera"
670    *
671    * @param root
672    * @param pattern
673    * @return
674    * @see https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob
675    */
 
676  0 toggle public static List<String> findMatches(String root, String pattern)
677    {
678  0 List<String> results = new ArrayList<>();
679  0 try
680    {
681  0 Path from = Paths.get(root);
682  0 findMatches(results, from, Arrays.asList(pattern.split("/")));
683    } catch (Throwable e)
684    {
685    // Paths.get can throw IllegalArgumentException
686  0 System.err.println(String.format("Error searching %s for %s: %s",
687    root, pattern, e.toString()));
688    }
689   
690  0 return results;
691    }
692   
693    /**
694    * A helper method that performs recursive search of file patterns and adds any
695    * 'leaf node' matches to the results list
696    *
697    * @param results
698    * @param from
699    * @param patterns
700    */
 
701  0 toggle protected static void findMatches(List<String> results, Path from,
702    List<String> patterns)
703    {
704  0 if (patterns.isEmpty())
705    {
706    /*
707    * reached end of recursion with all components matched
708    */
709  0 results.add(from.toString());
710  0 return;
711    }
712   
713  0 String pattern = patterns.get(0);
714  0 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(from,
715    pattern))
716    {
717  0 dirStream.forEach(p -> {
718   
719    /*
720    * matched a next level file - search below it
721    * (ignore non-directory non-leaf matches)
722    */
723  0 List<String> subList = patterns.subList(1, patterns.size());
724  0 if (subList.isEmpty() || p.toFile().isDirectory())
725    {
726  0 findMatches(results, p, subList);
727    }
728    });
729    } catch (IOException e)
730    {
731  0 System.err.println(String.format("Error searching %s: %s", pattern,
732    e.toString()));
733    }
734    }
735    }