Clover icon

Coverage Report

  1. Project Clover database Tue Mar 10 2026 14:58:44 GMT
  2. Package jalview.util

File FileUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
0% 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.00%
 

Contributing tests

No tests hitting this source file were found.

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  0 toggle public static List<File> getFilesFromGlob(String pattern)
58    {
59  0 return getFilesFromGlob(pattern, true);
60    }
61   
 
62  0 toggle public static List<File> getFilesFromGlob(String pattern,
63    boolean allowSingleFilenameThatDoesNotExist)
64    {
65  0 pattern = substituteHomeDir(pattern);
66  0 String relativePattern = pattern.startsWith(File.separator) ? null
67    : pattern;
68  0 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  0 int firstGlobChar = -1;
77  0 boolean foundGlobChar = false;
78  0 for (char c : new char[] { '*', '{', '?' })
79    {
80  0 if (pattern.indexOf(c) > -1
81    && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
82    {
83  0 firstGlobChar = pattern.indexOf(c);
84  0 foundGlobChar = true;
85    }
86    }
87  0 int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
88  0 if (foundGlobChar)
89    {
90  0 String pS = pattern.substring(0, lastFS + 1);
91  0 String rest = pattern.substring(lastFS + 1);
92  0 if ("".equals(pS))
93    {
94  0 pS = ".";
95    }
96  0 Path parentDir = Paths.get(pS);
97  0 if (parentDir.toFile().exists())
98    {
99  0 try
100    {
101  0 String glob = "glob:" + parentDir.toString() + File.separator
102    + rest;
103    // don't use Platform.isWin() as this class is used in getdown
104  0 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  0 PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
112  0 int maxDepth = rest.contains("**") ? 1028
113    : (int) (rest.chars()
114    .filter(ch -> ch == File.separatorChar).count())
115    + 1;
116   
117  0 Files.walkFileTree(parentDir,
118    EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
119    new SimpleFileVisitor<Path>()
120    {
 
121  0 toggle @Override
122    public FileVisitResult visitFile(Path path,
123    BasicFileAttributes attrs) throws IOException
124    {
125  0 if (pm.matches(path))
126    {
127  0 files.add(path.toFile());
128    }
129  0 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  0 File f = new File(pattern);
149  0 if (allowSingleFilenameThatDoesNotExist || f.exists())
150    {
151  0 files.add(f);
152    }
153    }
154  0 Collections.sort(files);
155   
156  0 return files;
157    }
158   
 
159  0 toggle public static List<String> getFilenamesFromGlob(String pattern)
160    {
161    // convert list of Files to list of File.getPath() Strings
162  0 return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
163    .collect(Collectors.toList());
164    }
165   
 
166  0 toggle public static String substituteHomeDir(String path)
167    {
168  0 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  0 toggle public static String getBasename(File file)
177    {
178  0 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  0 toggle public static String getBasenameOrExtension(File file, boolean extension)
190    {
191  0 if (file == null)
192  0 return null;
193   
194  0 String value = null;
195  0 String filename = file.getName();
196  0 int lastDot = filename.lastIndexOf('.');
197  0 if (lastDot > 0) // don't truncate if starts with '.'
198    {
199  0 value = extension ? filename.substring(lastDot + 1)
200    : filename.substring(0, lastDot);
201    }
202    else
203    {
204  0 value = extension ? "" : filename;
205    }
206  0 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  0 toggle public static String getDirname(File file)
214    {
215  0 if (file == null)
216  0 return null;
217   
218  0 String dirname = null;
219  0 File p = file.getParentFile();
220  0 if (p == null)
221    {
222  0 p = new File(".");
223    }
224  0 File d = new File(substituteHomeDir(p.getPath()));
225  0 dirname = d.getPath();
226  0 return dirname;
227    }
228   
 
229  0 toggle public static String convertWildcardsToPath(String value, String wildcard,
230    String dirname, String basename)
231    {
232  0 if (value == null)
233    {
234  0 return null;
235    }
236  0 StringBuilder path = new StringBuilder();
237  0 int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
238  0 int wildcardBeforeIndex = value.indexOf(wildcard);
239  0 if (lastFileSeparatorIndex > wildcard.length() - 1
240    && wildcardBeforeIndex < lastFileSeparatorIndex)
241    {
242  0 path.append(value.substring(0, wildcardBeforeIndex));
243  0 path.append(dirname);
244  0 path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
245    lastFileSeparatorIndex + 1));
246    }
247    else
248    {
249  0 path.append(value.substring(0, lastFileSeparatorIndex + 1));
250    }
251  0 int wildcardAfterIndex = value.indexOf(wildcard,
252    lastFileSeparatorIndex);
253  0 if (wildcardAfterIndex > lastFileSeparatorIndex)
254    {
255  0 path.append(value.substring(lastFileSeparatorIndex + 1,
256    wildcardAfterIndex));
257  0 path.append(basename);
258  0 path.append(value.substring(wildcardAfterIndex + wildcard.length()));
259    }
260    else
261    {
262  0 path.append(value.substring(lastFileSeparatorIndex + 1));
263    }
264  0 return path.toString();
265    }
266   
 
267  0 toggle public static File getParentDir(File file)
268    {
269  0 if (file == null)
270    {
271  0 return null;
272    }
273  0 File parentDir = file.getAbsoluteFile().getParentFile();
274  0 return parentDir;
275    }
276   
 
277  0 toggle public static boolean checkParentDir(File file, boolean mkdirs)
278    {
279  0 if (file == null)
280    {
281  0 return false;
282    }
283  0 File parentDir = getParentDir(file);
284  0 if (parentDir.exists())
285    {
286    // already exists, nothing to do so nothing to worry about!
287  0 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  0 toggle public static String getExtension(String filename)
319    {
320  0 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  0 toggle public static String getBase(String filename)
334    {
335  0 return getBaseOrExtension(filename, false);
336    }
337   
 
338  0 toggle public static String getBaseOrExtension(String filename0,
339    boolean extension)
340    {
341  0 if (filename0 == null)
342    {
343  0 return null;
344    }
345  0 String filename = filename0;
346  0 boolean isUrl = false;
347  0 if (HttpUtils.startsWithHttpOrHttps(filename))
348    {
349  0 try
350    {
351  0 URL url = new URL(filename);
352  0 filename = url.getPath();
353  0 isUrl = true;
354    } catch (MalformedURLException e)
355    {
356    // continue to treat as a filename
357    }
358    }
359  0 int dot = filename.lastIndexOf('.');
360  0 int slash = filename.lastIndexOf('/');
361  0 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  0 boolean hasExtension = dot > slash + 1;
368  0 if (extension)
369    {
370  0 return hasExtension ? filename.substring(dot + 1) : null;
371    }
372    else
373    {
374  0 dot = filename0.lastIndexOf('.');
375  0 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  0 toggle public static List<File> getMatchingVersionedFiles(String[] templates,
436    String[] roots, String[] versionWhitelist,
437    String[] versionBlacklist, String versionSeparator,
438    boolean exists)
439    {
440  0 Set<File> matchingFiles = new HashSet<>();
441  0 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  0 for (String template : templates)
451    {
452  0 ErrorLog.errPrintln("Using template '" + template + "'");
453  0 for (String root : roots)
454    {
455  0 ErrorLog.errPrintln("Using root '" + root + "'");
456   
457    // Blacklist. Use a file glob for version and see what's there
458  0 if (versionBlacklist != null)
459    {
460  0 String globMatch = String.format(template, root, "*");
461  0 ErrorLog.errPrintln("Using glob '" + globMatch + "'");
462  0 List<File> foundFiles = FileUtils.getFilesFromGlob(globMatch,
463    false);
464  0 for (File found : foundFiles)
465    {
466  0 ErrorLog.errPrintln("Checking " + found.getPath() + " is okay");
467  0 boolean add = true;
468  0 for (String notVersion : versionBlacklist)
469    {
470  0 StringBuilder vSB = new StringBuilder();
471  0 if (versionSeparator != null)
472    {
473  0 vSB.append(versionSeparator);
474    }
475  0 vSB.append(notVersion);
476  0 String versionString = vSB.toString();
477  0 if (String.format(template, root, versionString)
478    .equals(found.getPath()))
479    {
480  0 add = false;
481  0 ErrorLog.errPrintln(
482    "Not adding " + found.getPath() + ": version '"
483    + notVersion + "' is in the blacklist");
484  0 break;
485    }
486    }
487  0 if (add)
488    {
489  0 ErrorLog.errPrintln("Adding " + found.getPath() + " to list");
490  0 matchingFiles.add(found);
491    }
492    }
493   
494  0 if (versionSeparator != null)
495    {
496    // try without a version number too
497  0 String nonVersioned = String.format(template, root, "");
498  0 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  0 if (versionWhitelist != null)
506    {
507  0 ErrorLog.errPrintln("Adding " + versionWhitelist.length
508    + " whitelist versions");
509  0 for (String addVersion : versionWhitelist)
510    {
511  0 StringBuilder vSB = new StringBuilder();
512  0 if (versionSeparator != null)
513    {
514  0 vSB.append(versionSeparator);
515    }
516  0 vSB.append(addVersion);
517  0 String versionString = vSB.toString();
518  0 String versionPath = String.format(template, root,
519    versionString);
520  0 ErrorLog.errPrintln(
521    "Adding whitelist path '" + versionPath + "'");
522  0 File file = new File(versionPath);
523  0 if (file.exists() || !exists)
524    {
525  0 matchingFiles.add(file);
526    }
527    }
528   
529  0 if (versionSeparator != null)
530    {
531    // try without a version number too
532  0 String nonVersioned = String.format(template, root, "");
533  0 File file = new File(nonVersioned);
534  0 if (file.exists() || !exists)
535    {
536  0 matchingFiles.add(file);
537    }
538    }
539    }
540    }
541    }
542   
543  0 List<File> files = new ArrayList<File>();
544  0 files.addAll(matchingFiles);
545  0 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  0 toggle public static File getExecutable(String cmd, String binaryPath)
560    {
561  0 File file = new File(binaryPath, cmd);
562  0 if (!file.canExecute())
563    {
564  0 file = new File(binaryPath, cmd + ".exe");
565    {
566  0 if (!file.canExecute())
567    {
568  0 file = null;
569    }
570    }
571    }
572  0 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    }