Clover icon

Coverage Report

  1. Project Clover database Wed Nov 6 2024 14:47:21 GMT
  2. Package jalview.util

File FileUtils.java

 

Coverage histogram

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

Code metrics

84
166
17
1
503
380
68
0.41
9.76
17
4

Classes

Class Line # Actions
FileUtils 46 166 68
0.8277153482.8%
 

Contributing tests

This file is covered by 145 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    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 toggle public static List<File> getFilesFromGlob(String pattern)
55    {
56  164 return getFilesFromGlob(pattern, true);
57    }
58   
 
59  182 toggle 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  6436 toggle @Override
118    public FileVisitResult visitFile(Path path,
119    BasicFileAttributes attrs) throws IOException
120    {
121  6436 if (pm.matches(path))
122    {
123  3172 files.add(path.toFile());
124    }
125  6436 return FileVisitResult.CONTINUE;
126    }
127   
 
128  0 toggle @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 toggle 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 toggle 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 toggle 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 toggle public static String getExtension(File file)
181    {
182  10 return getBasenameOrExtension(file, true);
183    }
184   
 
185  88 toggle 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 toggle 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 toggle 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 toggle 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 toggle 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 Console.warn("Cautiously not running mkdirs on " + file.toString()
298    + " because the path to be made contains '..'");
299  0 return false;
300    }
301    }
302   
303  0 return parentDir.mkdirs();
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    /**
375    * Look for files that use a template, with two "%s" formatting entries, to
376    * look for files with the template substituted with combinations of root and
377    * version. If versionWhitelist is not null then these paths will be added. If
378    * versionBlacklist is not null then globbed versions will be looked for and
379    * these versions excluded. If both are given then both will be included. If
380    * separator is not null then it will be added before the version number, and
381    * additionally a path without the separator and version will be looked for or
382    * added if the whitelist or blacklist are supplied respectively.
383    *
384    * @param templates
385    * @param roots
386    * @param versionWhitelist
387    * @param versionBlacklist
388    * @param separator
389    * @return
390    */
 
391  7 toggle public static List<File> getMatchingVersionedFiles(String[] templates,
392    String[] roots, String[] versionWhitelist,
393    String[] versionBlacklist, String versionSeparator,
394    boolean exists)
395    {
396  7 Set<File> matchingFiles = new HashSet<>();
397  7 if (templates == null)
398    {
399  0 Console.debug(
400    "getMatchingVersionedFiles called with a null template array");
401  0 List<File> files = new ArrayList<File>();
402  0 files.addAll(matchingFiles);
403  0 return files;
404    }
405   
406  7 for (String template : templates)
407    {
408  7 Console.debug("Using template '" + template + "'");
409  7 for (String root : roots)
410    {
411  14 Console.debug("Using root '" + root + "'");
412   
413    // Blacklist. Use a file glob for version and see what's there
414  14 if (versionBlacklist != null)
415    {
416  10 String globMatch = String.format(template, root, "*");
417  10 Console.debug("Using glob '" + globMatch + "'");
418  10 List<File> foundFiles = FileUtils.getFilesFromGlob(globMatch,
419    false);
420  10 for (File found : foundFiles)
421    {
422  15 Console.debug("Checking " + found.getPath() + " is okay");
423  15 boolean add = true;
424  15 for (String notVersion : versionBlacklist)
425    {
426  20 StringBuilder vSB = new StringBuilder();
427  20 if (versionSeparator != null)
428    {
429  15 vSB.append(versionSeparator);
430    }
431  20 vSB.append(notVersion);
432  20 String versionString = vSB.toString();
433  20 if (String.format(template, root, versionString)
434    .equals(found.getPath()))
435    {
436  8 add = false;
437  8 Console.debug(
438    "Not adding " + found.getPath() + ": version '"
439    + notVersion + "' is in the blacklist");
440  8 break;
441    }
442    }
443  15 if (add)
444    {
445  7 Console.debug("Adding " + found.getPath() + " to list");
446  7 matchingFiles.add(found);
447    }
448    }
449   
450  10 if (versionSeparator != null)
451    {
452    // try without a version number too
453  8 String nonVersioned = String.format(template, root, "");
454  8 matchingFiles.addAll(
455    FileUtils.getFilesFromGlob(nonVersioned, false));
456    }
457    }
458   
459    // Whitelist. Just add a path for every whitelisted version (or check it
460    // exists).
461  14 if (versionWhitelist != null)
462    {
463  10 Console.debug("Adding " + versionWhitelist.length
464    + " whitelist versions");
465  10 for (String addVersion : versionWhitelist)
466    {
467  10 StringBuilder vSB = new StringBuilder();
468  10 if (versionSeparator != null)
469    {
470  8 vSB.append(versionSeparator);
471    }
472  10 vSB.append(addVersion);
473  10 String versionString = vSB.toString();
474  10 String versionPath = String.format(template, root,
475    versionString);
476  10 Console.debug("Adding whitelist path '" + versionPath + "'");
477  10 File file = new File(versionPath);
478  10 if (file.exists() || !exists)
479    {
480  6 matchingFiles.add(file);
481    }
482    }
483   
484  10 if (versionSeparator != null)
485    {
486    // try without a version number too
487  8 String nonVersioned = String.format(template, root, "");
488  8 File file = new File(nonVersioned);
489  8 if (file.exists() || !exists)
490    {
491  2 matchingFiles.add(file);
492    }
493    }
494    }
495    }
496    }
497   
498  7 List<File> files = new ArrayList<File>();
499  7 files.addAll(matchingFiles);
500  7 return files;
501    }
502   
503    }