Teaser Image

qnoid

Markos Charatzas - London, UK




/**
     * Polymorphic enums
     */
    public enum TypeFilter implements FileFilter {

        FILE
        {
            @Override
            public boolean accept(File file){
            return file != null && file.isFile();
            }
        }, 
        
        
        DIR
        {    
            @Override
            public boolean accept(File file){
            return file != null && file.isDirectory();
            }
        },     
        
        ALL
        {    
            @Override
            public boolean accept(File file){
            return file != null;
            }
        };
    }

    /**
     * Use to obtain different implementations to {@link FileFilter}s
     */
    public final class FileFilters
    {
        /**
         * A single instance will do since there our objects don't have any state
         */
        private final static FileFilters INSTANCE = new FileFilters();
        
        public static FileFilters fileFilters(){
        return INSTANCE;
        }        
        
        /*
         * Make it explicitly private so client code doesn't have to create one
         */
        private FileFilters(){}
            
        public RegExFilter newRegExFilter(String pattern){
        return new RegExFilter(pattern);
        }
        
        public RegExFilter newSuffixFilter(String suffix){
        return this.newRegExFilter("^.*" + Pattern.quote( suffix ) + "$");
        }
        
        /**
         * Create a new file filter that returns true if all filters are satisfied
         * 
         * @param filters the filter to include
         * @return a composite file filter
         */
        public FileFilter all(final FileFilter... filters)
        {
        return new FileFilter() {
                    
            @Override
            public boolean accept(File pathname)
            {
                for (FileFilter fileFilter : filters)
                {
                    if(!fileFilter.accept(pathname))
                        return false;
                }
                
            return true;
            }
        };
        }
    }

    /**
     * Extending file through composition
     */
    public class EnhancedFile
    {
        private final File file;
        
        /**
         * @param file the file to extend
         */
        public EnhancedFile(File file)
        {
            this.file = file;
        }

        private final List<File> getFilesRecursive( File file, FileFilter filter ) 
        {
            /*
             * If the file is normal end the recursion.  
             */
            if(file.isFile())
                return new ArrayList<File>();
            
            List<File> files = new ArrayList<File>();
            
            for ( File subdir : file.listFiles( TypeFilter.DIR ) ) {
                files.addAll( this.getFilesRecursive( subdir, filter ) );
            }
            
            files.addAll( Arrays.asList( file.listFiles( filter ) ) );
            
        return files;
        }
        
        public final List<File> getFilesRecursive( FileFilter filter ) {
        return this.getFilesRecursive(this.file, filter);
        }
    }

    /**
     * Verifying the signature of FileFilters#all
     */
    public class FileFiltersTest
    {
        private static final FileFilters FILE_FILTERS = FileFilters.fileFilters();

        @Test
        public void all() throws Exception
        {
            /*
             * Quoting @cahnory from @imperez post
             */
            String pattern = "([^:]+:(\\/\\/)?)?([^@]+@)?([^?/]+)/";        
            FileFilter all = FILE_FILTERS.all(TypeFilter.ALL, FILE_FILTERS.newRegExFilter(pattern));
        }
    }

Did you know that you can define (even @Override) methods to enums pretty much in the same way as classes?

Instead of having the #accept(File) method checking on the "type" to decide on the operation, you can move the operation to the type itself! :D

Personally I believe the SuffixFilter is a bit of an overhead to implement with inheritance to just encapsule the

"^.*" + Pattern.quote( suffix ) + "$"

Maybe a Factory is more appropriate. Which can also be used to create other file filters as well.

The #all makes it easy to have your RegExFilter decoupled from the TypeFilter.

Try to avoid static Util classes as are harder to extend or test. Instead you can use composition to "extend" a File to add more methods

Also notice how the stop condition for the recursion is easier to identify.

Kudoz to @imperez for his Regex to find the first part of a url post and @cahnory followup comment. Though it doesn't apply to files, I did remember his post while looking at yours :)