/* * Cuelib library for manipulating cue sheets. * Copyright (C) 2007-2008 Jan-Willem van den Broek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package jwbroek.io; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import java.util.regex.Pattern; /** * Convenience class for selecting files based on some criterium. * @author jwbroek */ public class FileSelector { /** * The logger for this class. */ private final static Logger logger = Logger.getLogger(FileSelector.class.getCanonicalName()); /** * FileFilter that accepts only directories. */ private static FileFilter dirsFileFilter = new FileFilter() { public boolean accept(final File file) { FileSelector.logger.entering(this.getClass().getCanonicalName(), "accept(File)", file); FileSelector.logger.exiting(this.getClass().getCanonicalName(), "accept(File)", file.isDirectory()); return file.isDirectory(); } }; /** * FileFilter that accepts only files (no directories). */ private static FileFilter filesFilter = new FileFilter() { public boolean accept(final File file) { FileSelector.logger.entering(this.getClass().getCanonicalName(), "accept(File)", file); FileSelector.logger.exiting(this.getClass().getCanonicalName(), "accept(File)", file.isFile()); return file.isFile(); } }; /** * Constructor. Should not be used as this class doesn't need to be instantiated. */ private FileSelector() { // Intentionally left blank (besides logging). This class doesn't need to be instantiated. FileSelector.logger.entering(FileSelector.class.getCanonicalName(), "FileSelector(File)"); FileSelector.logger.warning("jwbroek.io.FileSelector should not be instantiated."); FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "FileSelector(File)"); } /** * Get a FileFilter that accepts only directories. * @return A FileFilter that accepts only directories. */ public static FileFilter getDirsFilter() { FileSelector.logger.entering(FileSelector.class.getCanonicalName(), "getDirsFilter()"); FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "getDirsFilter()", FileSelector.dirsFileFilter); return FileSelector.dirsFileFilter; } /** * Get a FileFilter that accepts only files (no directories). * @return A FileFilter that accepts only files (no directories). */ public static FileFilter getFilesFilter() { FileSelector.logger.entering(FileSelector.class.getCanonicalName(), "getFilesFilter()"); FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "getFilesFilter()", FileSelector.filesFilter); return FileSelector.filesFilter; } /** * Get a FileFilter that will accept only files such that their canonical paths match the pattern. * @param pattern The pattern to match. * @return A FileFilter that will accept only files such that their canonical paths match the pattern. */ public static FileFilter getPathPatternFilter(final Pattern pattern) { FileSelector.logger.entering(FileSelector.class.getCanonicalName(), "getPathPatternFilter(Pattern)", pattern); FileFilter result = new FileFilter() { public boolean accept(final File file) { FileSelector.logger.entering(this.getClass().getCanonicalName(), "accept(File)", file); boolean result; try { result = pattern.matcher(file.getCanonicalPath()).matches(); } catch (IOException e) { result = false; } catch (SecurityException e) { result = false; } FileSelector.logger.fine ("PathPatternFilter " + (result?"accepted ":"did not accept '") + file.toString() + "'."); FileSelector.logger.exiting(this.getClass().getCanonicalName(), "accept(File)", result); return result; } }; FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "getPathPatternFilter(Pattern)", result); return result; } /** * Get a FileFilter that will accept only files such that their names match the pattern. * @param pattern The pattern to match. * @return A FileFilter that will accept only files such that their names match the pattern. */ public static FileFilter getFileNamePatternFilter(final Pattern pattern) { FileSelector.logger.entering(FileSelector.class.getCanonicalName(), "getFileNamePatternFilter(Pattern)", pattern); FileFilter result = new FileFilter() { public boolean accept(final File file) { FileSelector.logger.entering(this.getClass().getCanonicalName(), "accept(File)", file); boolean result; try { result = pattern.matcher(file.getName()).matches(); } catch (SecurityException e) { result = false; } FileSelector.logger.fine ("FileNamePatternFilter " + (result?"accepted ":"did not accept '") + file.toString() + "'."); FileSelector.logger.exiting(this.getClass().getCanonicalName(), "accept(File)", result); return result; } }; FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "getFileNamePatternFilter(Pattern)", result); return result; } /** * Get a FileFilter that combines all the specified FileFilters, such that it will only accept a file * if it is accepted by all specified FileFilters. The filters will be tested in order, so it is generally * preferable to put cheap and highly discriminating filters at low indices. * @param fileFilters The FileFilter instances to combine. * @return A FileFilter that combines all the specified FileFilters, such that it will only accept a file * if it is accepted by all specified FileFilters. */ public static FileFilter getCombinedFileFilter(final FileFilter ... fileFilters) { // The FileFilter ... parameter has lower (implicit) priority than Iterable<FileFilter>, so there is no // (directly) recursive call here. FileSelector.logger.entering (FileSelector.class.getCanonicalName(), "getCombinedFileFilter(FileFilter[])", fileFilters); FileFilter result = FileSelector.getCombinedFileFilter(fileFilters); FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "getCombinedFileFilter(FileFilter[])", result); return result; } /** * Get a FileFilter that combines all the specified FileFilters, such that it will only accept a file * if it is accepted by all specified FileFilters. The filters will be tested in order, so it is generally * preferable to put cheap and highly discriminating filters at low indices. * @param fileFilters The FileFilter instances to combine. * @return A FileFilter that combines all the specified FileFilters, such that it will only accept a file * if it is accepted by all specified FileFilters. */ public static FileFilter getCombinedFileFilter(final Iterable<FileFilter> fileFilters) { FileSelector.logger.entering (FileSelector.class.getCanonicalName(), "getCombinedFileFilter(Iterable<FileFilter>)", fileFilters); FileFilter result = new FileFilter() { public boolean accept(final File file) { FileSelector.logger.entering(FileSelector.class.getCanonicalName(), "accept(File)", file); boolean result = true; fileFilterLoop: for (FileFilter fileFilter : fileFilters) { if (! fileFilter.accept(file)) { result = false; break fileFilterLoop; } } FileSelector.logger.fine ("CombinedFileFilter " + (result?"accepted ":"did not accept '") + file.toString() + "'."); FileSelector.logger.exiting(FileSelector.class.getCanonicalName(), "accept(File)", result); return result; } }; FileSelector.logger.exiting (FileSelector.class.getCanonicalName(), "getCombinedFileFilter(Iterable<FileFilter>)", result); return result; } /** * Select files such that their canonical paths match the pattern. * @param baseFile Base to start looking for files. * @param pattern The pattern that the files must match. * @param recurseDepth The depth of subdirectories to recurse into. If 0, then only the base directory will * be processed (excluding any subfiles and subdirectories). If Long.MAX_VALUE, then recursion depth is * indefinite. If < 0, then no files will be processed. * @param considerBaseFile If set to false, then the baseFile will not be selected, even if it passes the * FileFilter. * @param keepGoing Whether or not to keep going when a SecurityException occurs. If true, * then such exceptions will be caught and the method will try to continue selecting more files. * @return A list of files such that they match the pattern. */ public static List<File> selectFiles ( final File baseFile , final Pattern pattern , final long recurseDepth , final boolean considerBaseFile , final boolean keepGoing ) { FileSelector.logger.entering ( FileSelector.class.getCanonicalName() , "selectFiles(File,Pattern,long,boolean,boolean)" , new Object [] {baseFile, pattern, recurseDepth, considerBaseFile, keepGoing} ); List<File> fileList = new ArrayList<File>(); selectFiles(baseFile, getPathPatternFilter(pattern), fileList, recurseDepth, considerBaseFile, keepGoing); FileSelector.logger.exiting (FileSelector.class.getCanonicalName(), "selectFiles(File,Pattern,long,boolean,boolean)", fileList); return fileList; } /** * Select files based on the specified criteria. * @param baseFile Base to start looking for files in. May be a proper file or a directory. If it passes the * filter, it will be added, unless the considerBaseFile parameter is set to false. * @param fileFilter The filter that the files must be tested against. * @param fileList Files that are matched will be added to this list. * @param recurseDepth The depth of subdirectories to recurse into. If 0, then only the base directory will * be processed (excluding any subfiles and subdirectories). If Long.MAX_VALUE, then recursion depth is * indefinite. If < 0, then no files will be processed. * @param considerBaseFile If set to false, then the baseFile will not be selected, even if it passes the * FileFilter. * @param keepGoing Whether or not to keep going when a SecurityException occurs. */ public static void selectFiles ( final File baseFile , final FileFilter fileFilter , final List<File> fileList , final long recurseDepth , final boolean considerBaseFile , final boolean keepGoing ) { FileSelector.logger.entering ( FileSelector.class.getCanonicalName() , "selectFiles(File,FileFilter,List<File>,long,boolean,boolean)" , new Object [] {baseFile, fileFilter, fileList, recurseDepth, considerBaseFile, keepGoing} ); // We've gone too deep, so stop. if (recurseDepth < 0) { FileSelector.logger.exiting (FileSelector.class.getCanonicalName(), "selectFiles(File,FileFilter,List<File>,long,boolean,boolean)"); return; } // Add the base file to the list, if it qualifies. try { if (considerBaseFile && fileFilter.accept(baseFile)) { fileList.add(baseFile); } } catch (SecurityException e) { if (!keepGoing) { FileSelector.logger.throwing (FileSelector.class.getCanonicalName(), "selectFiles(File,FileFilter,List<File>,long,boolean,boolean)", e); throw e; } } // Recurse if the base file is a directory. if (baseFile.isDirectory()) { try { // We have to check for null due to java bug 5086412. File [] subFiles = baseFile.listFiles(); if (subFiles != null) { long childRecurseDepth = recurseDepth==Long.MAX_VALUE?recurseDepth:recurseDepth-1; for (File childFile : subFiles) { selectFiles(childFile, fileFilter, fileList, childRecurseDepth, true, keepGoing); } } } catch (SecurityException e) { if (!keepGoing) { FileSelector.logger.throwing (FileSelector.class.getCanonicalName(), "selectFiles(File,FileFilter,List<File>,long,boolean,boolean)", e); throw e; } } } FileSelector.logger.exiting (FileSelector.class.getCanonicalName(), "selectFiles(File,FileFilter,List<File>,long,boolean,boolean)"); } }