Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / util / FileUtils.java
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.nio.file.FileSystems;
26 import java.nio.file.FileVisitOption;
27 import java.nio.file.FileVisitResult;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.PathMatcher;
31 import java.nio.file.Paths;
32 import java.nio.file.SimpleFileVisitor;
33 import java.nio.file.attribute.BasicFileAttributes;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.EnumSet;
37 import java.util.List;
38 import java.util.stream.Collectors;
39
40 import jalview.bin.Console;
41
42 public class FileUtils
43 {
44   /*
45    * Given string glob pattern (see
46    * https://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystem.html#getPathMatcher(java.lang.String)
47    * ) return a List of Files that match the pattern.
48    * Note this is a java style glob, not necessarily a bash-style glob, though there are sufficient similarities. 
49    */
50   public static List<File> getFilesFromGlob(String pattern)
51   {
52     return getFilesFromGlob(pattern, true);
53   }
54
55   public static List<File> getFilesFromGlob(String pattern,
56           boolean allowSingleFilenameThatDoesNotExist)
57   {
58     pattern = substituteHomeDir(pattern);
59     String relativePattern = pattern.startsWith(File.separator) ? null
60             : pattern;
61     List<File> files = new ArrayList<>();
62     /*
63      * For efficiency of the Files.walkFileTree(), let's find the longest path that doesn't need globbing.
64      * We look for the first glob character * { ? and then look for the last File.separator before that.
65      * Then we can reset the path to look at and shorten the globbing pattern.
66      * Relative paths can be used in pattern, which work from the pwd (though these are converted into
67      * full paths in the match). 
68      */
69     int firstGlobChar = -1;
70     boolean foundGlobChar = false;
71     for (char c : new char[] { '*', '{', '?' })
72     {
73       if (pattern.indexOf(c) > -1
74               && (pattern.indexOf(c) < firstGlobChar || !foundGlobChar))
75       {
76         firstGlobChar = pattern.indexOf(c);
77         foundGlobChar = true;
78       }
79     }
80     int lastFS = pattern.lastIndexOf(File.separatorChar, firstGlobChar);
81     if (foundGlobChar)
82     {
83       String pS = pattern.substring(0, lastFS + 1);
84       String rest = pattern.substring(lastFS + 1);
85       if ("".equals(pS))
86       {
87         pS = ".";
88       }
89       Path parentDir = Paths.get(pS);
90       if (parentDir.toFile().exists())
91       {
92         try
93         {
94           String glob = "glob:" + parentDir.toString() + File.separator
95                   + rest;
96           PathMatcher pm = FileSystems.getDefault().getPathMatcher(glob);
97           int maxDepth = rest.contains("**") ? 1028
98                   : (int) (rest.chars()
99                           .filter(ch -> ch == File.separatorChar).count())
100                           + 1;
101
102           Files.walkFileTree(parentDir,
103                   EnumSet.of(FileVisitOption.FOLLOW_LINKS), maxDepth,
104                   new SimpleFileVisitor<Path>()
105                   {
106                     @Override
107                     public FileVisitResult visitFile(Path path,
108                             BasicFileAttributes attrs) throws IOException
109                     {
110                       if (pm.matches(path))
111                       {
112                         files.add(path.toFile());
113                       }
114                       return FileVisitResult.CONTINUE;
115                     }
116
117                     @Override
118                     public FileVisitResult visitFileFailed(Path file,
119                             IOException exc) throws IOException
120                     {
121                       return FileVisitResult.CONTINUE;
122                     }
123                   });
124         } catch (IOException e)
125         {
126           e.printStackTrace();
127         }
128       }
129     }
130     else
131     {
132       // no wildcards
133       File f = new File(pattern);
134       if (allowSingleFilenameThatDoesNotExist || f.exists())
135       {
136         files.add(f);
137       }
138     }
139     Collections.sort(files);
140
141     return files;
142   }
143
144   public static List<String> getFilenamesFromGlob(String pattern)
145   {
146     // convert list of Files to list of File.getPath() Strings
147     return getFilesFromGlob(pattern).stream().map(f -> f.getPath())
148             .collect(Collectors.toList());
149   }
150
151   public static String substituteHomeDir(String path)
152   {
153     return path.startsWith("~" + File.separator)
154             ? System.getProperty("user.home") + path.substring(1)
155             : path;
156   }
157
158   /*
159    * This method returns the basename of File file
160    */
161   public static String getBasename(File file)
162   {
163     return getBasenameOrExtension(file, false);
164   }
165
166   /*
167    * This method returns the extension of File file.
168    */
169   public static String getExtension(File file)
170   {
171     return getBasenameOrExtension(file, true);
172   }
173
174   public static String getBasenameOrExtension(File file, boolean extension)
175   {
176     if (file == null)
177       return null;
178
179     String value = null;
180     String filename = file.getName();
181     int lastDot = filename.lastIndexOf('.');
182     if (lastDot > 0) // don't truncate if starts with '.'
183     {
184       value = extension ? filename.substring(lastDot + 1)
185               : filename.substring(0, lastDot);
186     }
187     else
188     {
189       value = extension ? "" : filename;
190     }
191     return value;
192   }
193
194   /*
195    * This method returns the dirname of the first --append or --open value. 
196    * Used primarily for substitutions in output filenames.
197    */
198   public static String getDirname(File file)
199   {
200     if (file == null)
201       return null;
202
203     String dirname = null;
204     File p = file.getParentFile();
205     if (p == null)
206     {
207       p = new File(".");
208     }
209     File d = new File(substituteHomeDir(p.getPath()));
210     dirname = d.getPath();
211     return dirname;
212   }
213
214   public static String convertWildcardsToPath(String value, String wildcard,
215           String dirname, String basename)
216   {
217     if (value == null)
218     {
219       return null;
220     }
221     StringBuilder path = new StringBuilder();
222     int lastFileSeparatorIndex = value.lastIndexOf(File.separatorChar);
223     int wildcardBeforeIndex = value.indexOf(wildcard);
224     if (lastFileSeparatorIndex > wildcard.length() - 1
225             && wildcardBeforeIndex < lastFileSeparatorIndex)
226     {
227       path.append(value.substring(0, wildcardBeforeIndex));
228       path.append(dirname);
229       path.append(value.substring(wildcardBeforeIndex + wildcard.length(),
230               lastFileSeparatorIndex + 1));
231     }
232     else
233     {
234       path.append(value.substring(0, lastFileSeparatorIndex + 1));
235     }
236     int wildcardAfterIndex = value.indexOf(wildcard,
237             lastFileSeparatorIndex);
238     if (wildcardAfterIndex > lastFileSeparatorIndex)
239     {
240       path.append(value.substring(lastFileSeparatorIndex + 1,
241               wildcardAfterIndex));
242       path.append(basename);
243       path.append(value.substring(wildcardAfterIndex + wildcard.length()));
244     }
245     else
246     {
247       path.append(value.substring(lastFileSeparatorIndex + 1));
248     }
249     return path.toString();
250   }
251
252   public static File getParentDir(File file)
253   {
254     if (file == null)
255     {
256       return null;
257     }
258     File parentDir = file.getAbsoluteFile().getParentFile();
259     return parentDir;
260   }
261
262   public static boolean checkParentDir(File file, boolean mkdirs)
263   {
264     if (file == null)
265     {
266       return false;
267     }
268     File parentDir = getParentDir(file);
269     if (parentDir.exists())
270     {
271       // already exists, nothing to do so nothing to worry about!
272       return true;
273     }
274
275     if (!mkdirs)
276     {
277       return false;
278     }
279
280     Path path = file.toPath();
281     for (int i = 0; i < path.getNameCount(); i++)
282     {
283       Path p = path.getName(i);
284       if ("..".equals(p.toString()))
285       {
286         Console.warn("Cautiously not running mkdirs on " + file.toString()
287                 + " because the path to be made contains '..'");
288         return false;
289       }
290     }
291
292     return parentDir.mkdirs();
293   }
294 }