001/* An actor that produces an array that lists the contents of a directory. 002 003 @Copyright (c) 2003-2018 The Regents of the University of California. 004 All rights reserved. 005 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the 009 above copyright notice and the following two paragraphs appear in all 010 copies of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION 2 026 COPYRIGHTENDKEY 027 */ 028package ptolemy.util; 029 030import java.io.File; 031import java.io.FilenameFilter; 032import java.util.Collections; 033import java.util.Comparator; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.regex.Matcher; 037import java.util.regex.Pattern; 038 039/////////////////////////////////////////////////////////////////// 040//// RecursiveFileFilter 041 042/** 043 A file name filter that can recursively list files in a directory, 044 including those in subdirectories. When a file name referring to a 045 directory is found, this filter lists the files within that directory again 046 with a new filter in this class. 047 048 @author Thomas Huining Feng, Christopher Brooks 049 @version $Id$ 050 @since Ptolemy II 10.0 051 @Pt.ProposedRating Yellow (tfeng) 052 @Pt.AcceptedRating Red (tfeng) 053 */ 054public class RecursiveFileFilter implements FilenameFilter { 055 056 /** Construct a recursive file filter. 057 * 058 * @param recursive Whether the filter should recursively list 059 * subdirectories. 060 * @param includeFiles Whether files should be included. 061 * @param includeDirectories Whether directories should be included. 062 */ 063 public RecursiveFileFilter(boolean recursive, boolean includeFiles, 064 boolean includeDirectories) { 065 this(recursive, includeFiles, includeDirectories, false, false, null); 066 } 067 068 /** Construct a recursive file filter. This method has four 069 * parameters to control whether files and directories are 070 * included. Not all combinations make sense, but are required 071 * because this code was refactored from two classes that had 072 * similar functionality. 073 * @param recursive Whether the filter should recursively list 074 * subdirectories. 075 * @param includeFiles Whether files should be included. 076 * @param includeDirectories Whether directories should be included. 077 * @param filesOnly Whether only files should be included 078 * @param directoriesOnly Whether only directories should be included. 079 * @param fileFilter The filter (with ? and * as wildcards) to filter 080 * the accepted file names. 081 */ 082 public RecursiveFileFilter(boolean recursive, boolean includeFiles, 083 boolean includeDirectories, boolean filesOnly, 084 boolean directoriesOnly, String fileFilter) { 085 this(recursive, includeFiles, includeDirectories, false, false, null, 086 false); 087 } 088 089 /** Construct a recursive file filter. This method has four 090 * parameters to control whether files and directories are 091 * included. Not all combinations make sense, but are required 092 * because this code was refactored from two classes that had 093 * similar functionality. 094 * @param recursive Whether the filter should recursively list 095 * subdirectories. 096 * @param includeFiles Whether files should be included. 097 * @param includeDirectories Whether directories should be included. 098 * @param filesOnly Whether only files should be included 099 * @param directoriesOnly Whether only directories should be included. 100 * @param fileFilter The filter (with ? and * as wildcards) to filter 101 * the accepted file names. 102 * @param escape True if a string with ? and * as wildcards is to 103 * be converted into a Java regular expression. The DirectoryListing 104 * actor calls this with a false value. 105 */ 106 public RecursiveFileFilter(boolean recursive, boolean includeFiles, 107 boolean includeDirectories, boolean filesOnly, 108 boolean directoriesOnly, String fileFilter, boolean escape) { 109 _recursive = recursive; 110 _includeFiles = includeFiles; 111 _includeDirectories = includeDirectories; 112 _filesOnly = filesOnly; 113 _directoriesOnly = directoriesOnly; 114 if (fileFilter != null && !fileFilter.equals("")) { 115 if (escape) { 116 // gt style, but gt calls listFiles() 117 _pattern = Pattern.compile(_escape(fileFilter)); 118 } else { 119 // DirectoryListin style, but DirectoryListing calls listFiles() 120 _pattern = Pattern.compile(fileFilter); 121 } 122 } else { 123 // Empty pattern for glob, which is .* 124 _pattern = Pattern.compile(".*"); 125 } 126 127 } 128 129 /** Return whether the file or directory name in the given directory is 130 * accepted. 131 * 132 * @param dir The directory. If directory is null, then it is 133 * likely that this method is being called to accept a URL and no 134 * File object is instantiated. If directory is not null, then a 135 * File object is instantiated. 136 * @param name The file or directory name within the given directory. 137 * @return Whether the name is accepted. 138 */ 139 @Override 140 public boolean accept(File dir, String name) { 141 File file = null; 142 boolean isDirectory = false; 143 boolean isFile = false; 144 if (dir != null) { 145 file = new File(dir, name); 146 isDirectory = file.isDirectory(); 147 isFile = file.isFile(); 148 } else { 149 // Could be a URL. 150 file = new File(name); 151 if (name.endsWith("/")) { 152 isDirectory = true; 153 } else { 154 isFile = true; 155 } 156 } 157 if (_match(isDirectory, isFile, name, file)) { 158 return true; 159 } 160 161 // file will be null if we are trying to accept a URL. 162 if (file != null && _recursive && isDirectory) { 163 file.list(this); 164 } 165 return false; 166 } 167 168 /** Return the list of all files and directories after the filtering. 169 * This must be called after all the directories are traversed. 170 * 171 * @return The list of files and directories. 172 */ 173 public List<File> getFiles() { 174 return _files; 175 } 176 177 /** List all the files and directories within the given directory. 178 * This method has four parameters to control whether 179 * files and directories are included. Not all combinations make sense. 180 * @param directory The directory. 181 * @param recursive Whether the filter should recursively list 182 * subdirectories. 183 * @param includeFiles Whether files should be included. 184 * @param includeDirectories Whether directories should be included. 185 * @param fileFilter The filter (with ? and * as wildcards) to filter 186 * the accepted file names. 187 * @return The array of all the files and directories found. 188 */ 189 public static File[] listFiles(File directory, boolean recursive, 190 boolean includeFiles, boolean includeDirectories, 191 String fileFilter) { 192 // gt uses this method. 193 return RecursiveFileFilter.listFiles(directory, recursive, includeFiles, 194 includeDirectories, false, false, fileFilter, true); 195 } 196 197 /** List all the files and directories within the given directory. 198 * This method has four parameters to control whether 199 * files and directories are included. Not all combinations make sense. 200 * @param directory The directory. 201 * @param recursive Whether the filter should recursively list 202 * subdirectories. 203 * @param includeFiles Whether files should be included. 204 * @param includeDirectories Whether directories should be included. 205 * @param fileFilter The filter (with ? and * as wildcards) to filter 206 * the accepted file names. 207 * @param escape True if a string with ? and * as wildcards is to 208 * be converted into a Java regular expression. The DirectoryListing 209 * actor calls this with a false value. 210 * @return The array of all the files and directories found. 211 */ 212 public static File[] listFiles(File directory, boolean recursive, 213 boolean includeFiles, boolean includeDirectories, String fileFilter, 214 boolean escape) { 215 // DirectoryListing uses this method. 216 return RecursiveFileFilter.listFiles(directory, recursive, includeFiles, 217 includeDirectories, false, false, fileFilter, escape); 218 } 219 220 /** List all the files and directories within the given directory. 221 * This method has four parameters to control whether 222 * files and directories are included. Not all combinations make sense. 223 * @param directory The directory. 224 * @param recursive Whether the filter should recursively list 225 * subdirectories. 226 * @param includeFiles Whether files should be included. 227 * @param includeDirectories Whether directories should be included. 228 * @param filesOnly Whether only files should be included 229 * @param directoriesOnly Whether only directories should be included. 230 * @param fileFilter The filter (with ? and * as wildcards) to filter 231 * the accepted file names. 232 * @return The array of all the files and directories found. 233 */ 234 public static File[] listFiles(File directory, boolean recursive, 235 boolean includeFiles, boolean includeDirectories, boolean filesOnly, 236 boolean directoriesOnly, String fileFilter) { 237 return RecursiveFileFilter.listFiles(directory, recursive, includeFiles, 238 includeDirectories, filesOnly, directoriesOnly, fileFilter, 239 true); 240 241 } 242 243 /** List all the files and directories within the given directory. 244 * This method has four parameters to control whether 245 * files and directories are included. Not all combinations make sense. 246 * @param directory The directory. 247 * @param recursive Whether the filter should recursively list 248 * subdirectories. 249 * @param includeFiles Whether files should be included. 250 * @param includeDirectories Whether directories should be included. 251 * @param filesOnly Whether only files should be included 252 * @param directoriesOnly Whether only directories should be included. 253 * @param fileFilter The filter (with ? and * as wildcards) to filter 254 * the accepted file names. 255 * @param escape True if a string with ? and * as wildcards is to 256 * be converted into a Java regular expression. The DirectoryListing 257 * actor calls this with a false value. 258 * @return The array of all the files and directories found. 259 */ 260 public static File[] listFiles(File directory, boolean recursive, 261 boolean includeFiles, boolean includeDirectories, boolean filesOnly, 262 boolean directoriesOnly, String fileFilter, boolean escape) { 263 RecursiveFileFilter filter = new RecursiveFileFilter(recursive, 264 includeFiles, includeDirectories, filesOnly, directoriesOnly, 265 fileFilter, escape); 266 directory.list(filter); 267 List<File> files = filter.getFiles(); 268 Collections.sort(files, new FileComparator()); 269 return files.toArray(new File[files.size()]); 270 } 271 272 /** Convert a string with ? and * as wildcards into a Java regular 273 * expression. 274 * 275 * @param string The string with file name wildcards. 276 * @return A regular expression. 277 */ 278 private String _escape(String string) { 279 String escaped = _ESCAPER.matcher(string).replaceAll("\\\\$1"); 280 return escaped.replaceAll("\\\\\\*", ".*").replaceAll("\\\\\\?", ".?"); 281 } 282 283 /** Return true if there is a match. 284 * @param isDirectory True if the name is a directory. 285 * @param isFile True if the name is a file 286 * @param name The name to check. 287 * @param file The file to be added if there is a match. 288 */ 289 private boolean _match(boolean isDirectory, boolean isFile, String name, 290 File file) { 291 if (((!_directoriesOnly && !_filesOnly) && ((isFile && _includeFiles) 292 || (isDirectory && _includeDirectories))) 293 || ((_filesOnly && isFile) || (_directoriesOnly && isDirectory) 294 || (!_directoriesOnly && !_filesOnly))) { 295 // ptolemy/domains/sdf/test/auto/filePortParameter.xml wants match.matches() here. 296 // ptolemy/actor/lib/test/auto/ExecRunDemos.xml wants match.find() here 297 298 // Avoid a NPE if the pattern is empty. 299 // See ptolemy/actor/lib/io/test/auto/DirectoryListingEmptyPattern.xml 300 // and https://projects.ecoinformatics.org/ecoinfo/issues/6233 301 if (_pattern != null) { 302 Matcher match = _pattern.matcher(name); 303 if (match.matches() || match.find()) { 304 _files.add(file); 305 return true; 306 } 307 } else { 308 return true; 309 } 310 } 311 return false; 312 } 313 314 /** The pattern for the wildcard conversion. 315 */ 316 private final Pattern _ESCAPER = Pattern.compile("([^a-zA-z0-9])"); 317 318 /** Whether only directories should be included. 319 */ 320 private boolean _directoriesOnly; 321 322 /** The list the recently found files and directories. 323 */ 324 private List<File> _files = new LinkedList<File>(); 325 326 /** Whether only files should be included. 327 */ 328 private boolean _filesOnly; 329 330 /** Whether directories should be included. 331 */ 332 private boolean _includeDirectories; 333 334 /** Whether files should be included. 335 */ 336 private boolean _includeFiles; 337 338 /** The pattern for the filter. 339 */ 340 private Pattern _pattern; 341 342 /** Whether the filter should recursively list subdirectories. 343 */ 344 private boolean _recursive; 345 346 /////////////////////////////////////////////////////////////////// 347 //// FileComparator 348 349 /** 350 A comparator to sort file names. 351 352 @author Thomas Huining Feng 353 @version $Id$ 354 @since Ptolemy II 10.0 355 @Pt.ProposedRating Yellow (tfeng) 356 @Pt.AcceptedRating Red (tfeng) 357 */ 358 private static class FileComparator implements Comparator<File> { 359 360 /** Compare two files with their names. 361 * 362 * @param file1 The first file. 363 * @param file2 The second file. 364 * @return -1, 0 or 1 if file1 is less than, equal to or greater 365 * than file2. 366 */ 367 @Override 368 public int compare(File file1, File file2) { 369 return file1.getAbsolutePath().compareTo(file2.getAbsolutePath()); 370 } 371 } 372}