001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.io.filefilter;
018
019import java.io.File;
020import java.io.FileFilter;
021import java.io.FilenameFilter;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.Date;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Set;
029
030import org.apache.commons.io.FileUtils;
031import org.apache.commons.io.IOCase;
032
033/**
034 * Useful utilities for working with file filters. It provides access to all
035 * file filter implementations in this package so you don't have to import
036 * every class you use.
037 *
038 * @since 1.0
039 *
040 */
041public class FileFilterUtils {
042
043    /**
044     * FileFilterUtils is not normally instantiated.
045     */
046    public FileFilterUtils() {
047    }
048
049    //-----------------------------------------------------------------------
050
051    /**
052     * <p>
053     * Applies an {@link IOFileFilter} to the provided {@link File}
054     * objects. The resulting array is a subset of the original file list that
055     * matches the provided filter.
056     * </p>
057     *
058     * <p>
059     * The {@link Set} returned by this method is not guaranteed to be thread safe.
060     * </p>
061     *
062     * <pre>
063     * Set&lt;File&gt; allFiles = ...
064     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
065     *     FileFilterUtils.suffixFileFilter(".java"));
066     * </pre>
067     * @param filter the filter to apply to the set of files.
068     * @param files the array of files to apply the filter to.
069     *
070     * @return a subset of <code>files</code> that is accepted by the
071     *         file filter.
072     * @throws IllegalArgumentException if the filter is {@code null}
073     *         or <code>files</code> contains a {@code null} value.
074     *
075     * @since 2.0
076     */
077    public static File[] filter(final IOFileFilter filter, final File... files) {
078        if (filter == null) {
079            throw new IllegalArgumentException("file filter is null");
080        }
081        if (files == null) {
082            return FileUtils.EMPTY_FILE_ARRAY;
083        }
084        final List<File> acceptedFiles = new ArrayList<>();
085        for (final File file : files) {
086            if (file == null) {
087                throw new IllegalArgumentException("file array contains null");
088            }
089            if (filter.accept(file)) {
090                acceptedFiles.add(file);
091            }
092        }
093        return acceptedFiles.toArray(FileUtils.EMPTY_FILE_ARRAY);
094    }
095
096    /**
097     * <p>
098     * Applies an {@link IOFileFilter} to the provided {@link File}
099     * objects. The resulting array is a subset of the original file list that
100     * matches the provided filter.
101     * </p>
102     *
103     * <p>
104     * The {@link Set} returned by this method is not guaranteed to be thread safe.
105     * </p>
106     *
107     * <pre>
108     * Set&lt;File&gt; allFiles = ...
109     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
110     *     FileFilterUtils.suffixFileFilter(".java"));
111     * </pre>
112     * @param filter the filter to apply to the set of files.
113     * @param files the array of files to apply the filter to.
114     *
115     * @return a subset of <code>files</code> that is accepted by the
116     *         file filter.
117     * @throws IllegalArgumentException if the filter is {@code null}
118     *         or <code>files</code> contains a {@code null} value.
119     *
120     * @since 2.0
121     */
122    public static File[] filter(final IOFileFilter filter, final Iterable<File> files) {
123        final List<File> acceptedFiles = filterList(filter, files);
124        return acceptedFiles.toArray(FileUtils.EMPTY_FILE_ARRAY);
125    }
126
127    /**
128     * <p>
129     * Applies an {@link IOFileFilter} to the provided {@link File}
130     * objects. The resulting list is a subset of the original files that
131     * matches the provided filter.
132     * </p>
133     *
134     * <p>
135     * The {@link List} returned by this method is not guaranteed to be thread safe.
136     * </p>
137     *
138     * <pre>
139     * List&lt;File&gt; filesAndDirectories = ...
140     * List&lt;File&gt; directories = FileFilterUtils.filterList(filesAndDirectories,
141     *     FileFilterUtils.directoryFileFilter());
142     * </pre>
143     * @param filter the filter to apply to each files in the list.
144     * @param files the collection of files to apply the filter to.
145     *
146     * @return a subset of <code>files</code> that is accepted by the
147     *         file filter.
148     * @throws IllegalArgumentException if the filter is {@code null}
149     *         or <code>files</code> contains a {@code null} value.
150     * @since 2.0
151     */
152    public static List<File> filterList(final IOFileFilter filter, final Iterable<File> files) {
153        return filter(filter, files, new ArrayList<File>());
154    }
155
156    /**
157     * <p>
158     * Applies an {@link IOFileFilter} to the provided {@link File}
159     * objects. The resulting list is a subset of the original files that
160     * matches the provided filter.
161     * </p>
162     *
163     * <p>
164     * The {@link List} returned by this method is not guaranteed to be thread safe.
165     * </p>
166     *
167     * <pre>
168     * List&lt;File&gt; filesAndDirectories = ...
169     * List&lt;File&gt; directories = FileFilterUtils.filterList(filesAndDirectories,
170     *     FileFilterUtils.directoryFileFilter());
171     * </pre>
172     * @param filter the filter to apply to each files in the list.
173     * @param files the collection of files to apply the filter to.
174     *
175     * @return a subset of <code>files</code> that is accepted by the
176     *         file filter.
177     * @throws IllegalArgumentException if the filter is {@code null}
178     *         or <code>files</code> contains a {@code null} value.
179     * @since 2.0
180     */
181    public static List<File> filterList(final IOFileFilter filter, final File... files) {
182        final File[] acceptedFiles = filter(filter, files);
183        return Arrays.asList(acceptedFiles);
184    }
185
186    /**
187     * <p>
188     * Applies an {@link IOFileFilter} to the provided {@link File}
189     * objects. The resulting set is a subset of the original file list that
190     * matches the provided filter.
191     * </p>
192     *
193     * <p>
194     * The {@link Set} returned by this method is not guaranteed to be thread safe.
195     * </p>
196     *
197     * <pre>
198     * Set&lt;File&gt; allFiles = ...
199     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
200     *     FileFilterUtils.suffixFileFilter(".java"));
201     * </pre>
202     * @param filter the filter to apply to the set of files.
203     * @param files the collection of files to apply the filter to.
204     *
205     * @return a subset of <code>files</code> that is accepted by the
206     *         file filter.
207     * @throws IllegalArgumentException if the filter is {@code null}
208     *         or <code>files</code> contains a {@code null} value.
209     *
210     * @since 2.0
211     */
212    public static Set<File> filterSet(final IOFileFilter filter, final File... files) {
213        final File[] acceptedFiles = filter(filter, files);
214        return new HashSet<>(Arrays.asList(acceptedFiles));
215    }
216
217    /**
218     * <p>
219     * Applies an {@link IOFileFilter} to the provided {@link File}
220     * objects. The resulting set is a subset of the original file list that
221     * matches the provided filter.
222     * </p>
223     *
224     * <p>
225     * The {@link Set} returned by this method is not guaranteed to be thread safe.
226     * </p>
227     *
228     * <pre>
229     * Set&lt;File&gt; allFiles = ...
230     * Set&lt;File&gt; javaFiles = FileFilterUtils.filterSet(allFiles,
231     *     FileFilterUtils.suffixFileFilter(".java"));
232     * </pre>
233     * @param filter the filter to apply to the set of files.
234     * @param files the collection of files to apply the filter to.
235     *
236     * @return a subset of <code>files</code> that is accepted by the
237     *         file filter.
238     * @throws IllegalArgumentException if the filter is {@code null}
239     *         or <code>files</code> contains a {@code null} value.
240     *
241     * @since 2.0
242     */
243    public static Set<File> filterSet(final IOFileFilter filter, final Iterable<File> files) {
244        return filter(filter, files, new HashSet<File>());
245    }
246
247    /**
248     * <p>
249     * Applies an {@link IOFileFilter} to the provided {@link File}
250     * objects and appends the accepted files to the other supplied collection.
251     * </p>
252     *
253     * <pre>
254     * List&lt;File&gt; files = ...
255     * List&lt;File&gt; directories = FileFilterUtils.filterList(files,
256     *     FileFilterUtils.sizeFileFilter(FileUtils.FIFTY_MB),
257     *         new ArrayList&lt;File&gt;());
258     * </pre>
259     * @param filter the filter to apply to the collection of files.
260     * @param files the collection of files to apply the filter to.
261     * @param acceptedFiles the list of files to add accepted files to.
262     *
263     * @param <T> the type of the file collection.
264     * @return a subset of <code>files</code> that is accepted by the
265     *         file filter.
266     * @throws IllegalArgumentException if the filter is {@code null}
267     *         or <code>files</code> contains a {@code null} value.
268     */
269    private static <T extends Collection<File>> T filter(final IOFileFilter filter,
270            final Iterable<File> files, final T acceptedFiles) {
271        if (filter == null) {
272            throw new IllegalArgumentException("file filter is null");
273        }
274        if (files != null) {
275            for (final File file : files) {
276                if (file == null) {
277                    throw new IllegalArgumentException("file collection contains null");
278                }
279                if (filter.accept(file)) {
280                    acceptedFiles.add(file);
281                }
282            }
283        }
284        return acceptedFiles;
285    }
286
287    /**
288     * Returns a filter that returns true if the file name starts with the specified text.
289     *
290     * @param prefix  the file name prefix
291     * @return a prefix checking filter
292     * @see PrefixFileFilter
293     */
294    public static IOFileFilter prefixFileFilter(final String prefix) {
295        return new PrefixFileFilter(prefix);
296    }
297
298    /**
299     * Returns a filter that returns true if the file name starts with the specified text.
300     *
301     * @param prefix  the file name prefix
302     * @param caseSensitivity  how to handle case sensitivity, null means case-sensitive
303     * @return a prefix checking filter
304     * @see PrefixFileFilter
305     * @since 2.0
306     */
307    public static IOFileFilter prefixFileFilter(final String prefix, final IOCase caseSensitivity) {
308        return new PrefixFileFilter(prefix, caseSensitivity);
309    }
310
311    /**
312     * Returns a filter that returns true if the file name ends with the specified text.
313     *
314     * @param suffix  the file name suffix
315     * @return a suffix checking filter
316     * @see SuffixFileFilter
317     */
318    public static IOFileFilter suffixFileFilter(final String suffix) {
319        return new SuffixFileFilter(suffix);
320    }
321
322    /**
323     * Returns a filter that returns true if the file name ends with the specified text.
324     *
325     * @param suffix  the file name suffix
326     * @param caseSensitivity  how to handle case sensitivity, null means case-sensitive
327     * @return a suffix checking filter
328     * @see SuffixFileFilter
329     * @since 2.0
330     */
331    public static IOFileFilter suffixFileFilter(final String suffix, final IOCase caseSensitivity) {
332        return new SuffixFileFilter(suffix, caseSensitivity);
333    }
334
335    /**
336     * Returns a filter that returns true if the file name matches the specified text.
337     *
338     * @param name  the file name
339     * @return a name checking filter
340     * @see NameFileFilter
341     */
342    public static IOFileFilter nameFileFilter(final String name) {
343        return new NameFileFilter(name);
344    }
345
346    /**
347     * Returns a filter that returns true if the file name matches the specified text.
348     *
349     * @param name  the file name
350     * @param caseSensitivity  how to handle case sensitivity, null means case-sensitive
351     * @return a name checking filter
352     * @see NameFileFilter
353     * @since 2.0
354     */
355    public static IOFileFilter nameFileFilter(final String name, final IOCase caseSensitivity) {
356        return new NameFileFilter(name, caseSensitivity);
357    }
358
359    /**
360     * Returns a filter that checks if the file is a directory.
361     *
362     * @return file filter that accepts only directories and not files
363     * @see DirectoryFileFilter#DIRECTORY
364     */
365    public static IOFileFilter directoryFileFilter() {
366        return DirectoryFileFilter.DIRECTORY;
367    }
368
369    /**
370     * Returns a filter that checks if the file is a file (and not a directory).
371     *
372     * @return file filter that accepts only files and not directories
373     * @see FileFileFilter#FILE
374     */
375    public static IOFileFilter fileFileFilter() {
376        return FileFileFilter.FILE;
377    }
378
379    //-----------------------------------------------------------------------
380    /**
381     * Returns a filter that ANDs the two specified filters.
382     *
383     * @param filter1  the first filter
384     * @param filter2  the second filter
385     * @return a filter that ANDs the two specified filters
386     * @see #and(IOFileFilter...)
387     * @see AndFileFilter
388     * @deprecated use {@link #and(IOFileFilter...)}
389     */
390    @Deprecated
391    public static IOFileFilter andFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) {
392        return new AndFileFilter(filter1, filter2);
393    }
394
395    /**
396     * Returns a filter that ORs the two specified filters.
397     *
398     * @param filter1  the first filter
399     * @param filter2  the second filter
400     * @return a filter that ORs the two specified filters
401     * @see #or(IOFileFilter...)
402     * @see OrFileFilter
403     * @deprecated use {@link #or(IOFileFilter...)}
404     */
405    @Deprecated
406    public static IOFileFilter orFileFilter(final IOFileFilter filter1, final IOFileFilter filter2) {
407        return new OrFileFilter(filter1, filter2);
408    }
409
410    /**
411     * Returns a filter that ANDs the specified filters.
412     *
413     * @param filters the IOFileFilters that will be ANDed together.
414     * @return a filter that ANDs the specified filters
415     *
416     * @throws IllegalArgumentException if the filters are null or contain a
417     *         null value.
418     * @see AndFileFilter
419     * @since 2.0
420     */
421    public static IOFileFilter and(final IOFileFilter... filters) {
422        return new AndFileFilter(toList(filters));
423    }
424
425    /**
426     * Returns a filter that ORs the specified filters.
427     *
428     * @param filters the IOFileFilters that will be ORed together.
429     * @return a filter that ORs the specified filters
430     *
431     * @throws IllegalArgumentException if the filters are null or contain a
432     *         null value.
433     * @see OrFileFilter
434     * @since 2.0
435     */
436    public static IOFileFilter or(final IOFileFilter... filters) {
437        return new OrFileFilter(toList(filters));
438    }
439
440    /**
441     * Create a List of file filters.
442     *
443     * @param filters The file filters
444     * @return The list of file filters
445     * @throws IllegalArgumentException if the filters are null or contain a
446     *         null value.
447     * @since 2.0
448     */
449    public static List<IOFileFilter> toList(final IOFileFilter... filters) {
450        if (filters == null) {
451            throw new IllegalArgumentException("The filters must not be null");
452        }
453        final List<IOFileFilter> list = new ArrayList<>(filters.length);
454        for (int i = 0; i < filters.length; i++) {
455            if (filters[i] == null) {
456                throw new IllegalArgumentException("The filter[" + i + "] is null");
457            }
458            list.add(filters[i]);
459        }
460        return list;
461    }
462
463    /**
464     * Returns a filter that NOTs the specified filter.
465     *
466     * @param filter  the filter to invert
467     * @return a filter that NOTs the specified filter
468     * @see NotFileFilter
469     */
470    public static IOFileFilter notFileFilter(final IOFileFilter filter) {
471        return new NotFileFilter(filter);
472    }
473
474    //-----------------------------------------------------------------------
475    /**
476     * Returns a filter that always returns true.
477     *
478     * @return a true filter
479     * @see TrueFileFilter#TRUE
480     */
481    public static IOFileFilter trueFileFilter() {
482        return TrueFileFilter.TRUE;
483    }
484
485    /**
486     * Returns a filter that always returns false.
487     *
488     * @return a false filter
489     * @see FalseFileFilter#FALSE
490     */
491    public static IOFileFilter falseFileFilter() {
492        return FalseFileFilter.FALSE;
493    }
494
495    //-----------------------------------------------------------------------
496    /**
497     * Returns an <code>IOFileFilter</code> that wraps the
498     * <code>FileFilter</code> instance.
499     *
500     * @param filter  the filter to be wrapped
501     * @return a new filter that implements IOFileFilter
502     * @see DelegateFileFilter
503     */
504    public static IOFileFilter asFileFilter(final FileFilter filter) {
505        return new DelegateFileFilter(filter);
506    }
507
508    /**
509     * Returns an <code>IOFileFilter</code> that wraps the
510     * <code>FilenameFilter</code> instance.
511     *
512     * @param filter  the filter to be wrapped
513     * @return a new filter that implements IOFileFilter
514     * @see DelegateFileFilter
515     */
516    public static IOFileFilter asFileFilter(final FilenameFilter filter) {
517        return new DelegateFileFilter(filter);
518    }
519
520    //-----------------------------------------------------------------------
521    /**
522     * Returns a filter that returns true if the file was last modified before
523     * or at the specified cutoff time.
524     *
525     * @param cutoff  the time threshold
526     * @return an appropriately configured age file filter
527     * @see AgeFileFilter
528     * @since 1.2
529     */
530    public static IOFileFilter ageFileFilter(final long cutoff) {
531        return new AgeFileFilter(cutoff);
532    }
533
534    /**
535     * Returns a filter that filters files based on a cutoff time.
536     *
537     * @param cutoff  the time threshold
538     * @param acceptOlder  if true, older files get accepted, if false, newer
539     * @return an appropriately configured age file filter
540     * @see AgeFileFilter
541     * @since 1.2
542     */
543    public static IOFileFilter ageFileFilter(final long cutoff, final boolean acceptOlder) {
544        return new AgeFileFilter(cutoff, acceptOlder);
545    }
546
547    /**
548     * Returns a filter that returns true if the file was last modified before
549     * or at the specified cutoff date.
550     *
551     * @param cutoffDate  the time threshold
552     * @return an appropriately configured age file filter
553     * @see AgeFileFilter
554     * @since 1.2
555     */
556    public static IOFileFilter ageFileFilter(final Date cutoffDate) {
557        return new AgeFileFilter(cutoffDate);
558    }
559
560    /**
561     * Returns a filter that filters files based on a cutoff date.
562     *
563     * @param cutoffDate  the time threshold
564     * @param acceptOlder  if true, older files get accepted, if false, newer
565     * @return an appropriately configured age file filter
566     * @see AgeFileFilter
567     * @since 1.2
568     */
569    public static IOFileFilter ageFileFilter(final Date cutoffDate, final boolean acceptOlder) {
570        return new AgeFileFilter(cutoffDate, acceptOlder);
571    }
572
573    /**
574     * Returns a filter that returns true if the file was last modified before
575     * or at the same time as the specified reference file.
576     *
577     * @param cutoffReference  the file whose last modification
578     *        time is used as the threshold age of the files
579     * @return an appropriately configured age file filter
580     * @see AgeFileFilter
581     * @since 1.2
582     */
583    public static IOFileFilter ageFileFilter(final File cutoffReference) {
584        return new AgeFileFilter(cutoffReference);
585    }
586
587    /**
588     * Returns a filter that filters files based on a cutoff reference file.
589     *
590     * @param cutoffReference  the file whose last modification
591     *        time is used as the threshold age of the files
592     * @param acceptOlder  if true, older files get accepted, if false, newer
593     * @return an appropriately configured age file filter
594     * @see AgeFileFilter
595     * @since 1.2
596     */
597    public static IOFileFilter ageFileFilter(final File cutoffReference, final boolean acceptOlder) {
598        return new AgeFileFilter(cutoffReference, acceptOlder);
599    }
600
601    //-----------------------------------------------------------------------
602    /**
603     * Returns a filter that returns true if the file is bigger than a certain size.
604     *
605     * @param threshold  the file size threshold
606     * @return an appropriately configured SizeFileFilter
607     * @see SizeFileFilter
608     * @since 1.2
609     */
610    public static IOFileFilter sizeFileFilter(final long threshold) {
611        return new SizeFileFilter(threshold);
612    }
613
614    /**
615     * Returns a filter that filters based on file size.
616     *
617     * @param threshold  the file size threshold
618     * @param acceptLarger  if true, larger files get accepted, if false, smaller
619     * @return an appropriately configured SizeFileFilter
620     * @see SizeFileFilter
621     * @since 1.2
622     */
623    public static IOFileFilter sizeFileFilter(final long threshold, final boolean acceptLarger) {
624        return new SizeFileFilter(threshold, acceptLarger);
625    }
626
627    /**
628     * Returns a filter that accepts files whose size is &gt;= minimum size
629     * and &lt;= maximum size.
630     *
631     * @param minSizeInclusive the minimum file size (inclusive)
632     * @param maxSizeInclusive the maximum file size (inclusive)
633     * @return an appropriately configured IOFileFilter
634     * @see SizeFileFilter
635     * @since 1.3
636     */
637    public static IOFileFilter sizeRangeFileFilter(final long minSizeInclusive, final long maxSizeInclusive ) {
638        final IOFileFilter minimumFilter = new SizeFileFilter(minSizeInclusive, true);
639        final IOFileFilter maximumFilter = new SizeFileFilter(maxSizeInclusive + 1L, false);
640        return new AndFileFilter(minimumFilter, maximumFilter);
641    }
642
643    /**
644     * Returns a filter that accepts files that begin with the provided magic
645     * number.
646     *
647     * @param magicNumber the magic number (byte sequence) to match at the
648     *        beginning of each file.
649     *
650     * @return an IOFileFilter that accepts files beginning with the provided
651     *         magic number.
652     *
653     * @throws IllegalArgumentException if <code>magicNumber</code> is
654     *         {@code null} or the empty String.
655     * @see MagicNumberFileFilter
656     * @since 2.0
657     */
658    public static IOFileFilter magicNumberFileFilter(final String magicNumber) {
659        return new MagicNumberFileFilter(magicNumber);
660    }
661
662    /**
663     * Returns a filter that accepts files that contains the provided magic
664     * number at a specified offset within the file.
665     *
666     * @param magicNumber the magic number (byte sequence) to match at the
667     *        provided offset in each file.
668     * @param offset the offset within the files to look for the magic number.
669     *
670     * @return an IOFileFilter that accepts files containing the magic number
671     *         at the specified offset.
672     *
673     * @throws IllegalArgumentException if <code>magicNumber</code> is
674     *         {@code null} or the empty String, or if offset is a
675     *         negative number.
676     * @see MagicNumberFileFilter
677     * @since 2.0
678     */
679    public static IOFileFilter magicNumberFileFilter(final String magicNumber, final long offset) {
680        return new MagicNumberFileFilter(magicNumber, offset);
681    }
682
683    /**
684     * Returns a filter that accepts files that begin with the provided magic
685     * number.
686     *
687     * @param magicNumber the magic number (byte sequence) to match at the
688     *        beginning of each file.
689     *
690     * @return an IOFileFilter that accepts files beginning with the provided
691     *         magic number.
692     *
693     * @throws IllegalArgumentException if <code>magicNumber</code> is
694     *         {@code null} or is of length zero.
695     * @see MagicNumberFileFilter
696     * @since 2.0
697     */
698    public static IOFileFilter magicNumberFileFilter(final byte[] magicNumber) {
699        return new MagicNumberFileFilter(magicNumber);
700    }
701
702    /**
703     * Returns a filter that accepts files that contains the provided magic
704     * number at a specified offset within the file.
705     *
706     * @param magicNumber the magic number (byte sequence) to match at the
707     *        provided offset in each file.
708     * @param offset the offset within the files to look for the magic number.
709     *
710     * @return an IOFileFilter that accepts files containing the magic number
711     *         at the specified offset.
712     *
713     * @throws IllegalArgumentException if <code>magicNumber</code> is
714     *         {@code null}, or contains no bytes, or <code>offset</code>
715     *         is a negative number.
716     * @see MagicNumberFileFilter
717     * @since 2.0
718     */
719    public static IOFileFilter magicNumberFileFilter(final byte[] magicNumber, final long offset) {
720        return new MagicNumberFileFilter(magicNumber, offset);
721    }
722
723    //-----------------------------------------------------------------------
724    /* Constructed on demand and then cached */
725    private static final IOFileFilter cvsFilter = notFileFilter(
726            and(directoryFileFilter(), nameFileFilter("CVS")));
727
728    /* Constructed on demand and then cached */
729    private static final IOFileFilter svnFilter = notFileFilter(
730            and(directoryFileFilter(), nameFileFilter(".svn")));
731
732    /**
733     * Decorates a filter to make it ignore CVS directories.
734     * Passing in {@code null} will return a filter that accepts everything
735     * except CVS directories.
736     *
737     * @param filter  the filter to decorate, null means an unrestricted filter
738     * @return the decorated filter, never null
739     * @since 1.1 (method existed but had bug in 1.0)
740     */
741    public static IOFileFilter makeCVSAware(final IOFileFilter filter) {
742        return filter == null ? cvsFilter : and(filter, cvsFilter);
743    }
744
745    /**
746     * Decorates a filter to make it ignore SVN directories.
747     * Passing in {@code null} will return a filter that accepts everything
748     * except SVN directories.
749     *
750     * @param filter  the filter to decorate, null means an unrestricted filter
751     * @return the decorated filter, never null
752     * @since 1.1
753     */
754    public static IOFileFilter makeSVNAware(final IOFileFilter filter) {
755        return filter == null ? svnFilter : and(filter, svnFilter);
756    }
757
758    //-----------------------------------------------------------------------
759    /**
760     * Decorates a filter so that it only applies to directories and not to files.
761     *
762     * @param filter  the filter to decorate, null means an unrestricted filter
763     * @return the decorated filter, never null
764     * @see DirectoryFileFilter#DIRECTORY
765     * @since 1.3
766     */
767    public static IOFileFilter makeDirectoryOnly(final IOFileFilter filter) {
768        if (filter == null) {
769            return DirectoryFileFilter.DIRECTORY;
770        }
771        return new AndFileFilter(DirectoryFileFilter.DIRECTORY, filter);
772    }
773
774    /**
775     * Decorates a filter so that it only applies to files and not to directories.
776     *
777     * @param filter  the filter to decorate, null means an unrestricted filter
778     * @return the decorated filter, never null
779     * @see FileFileFilter#FILE
780     * @since 1.3
781     */
782    public static IOFileFilter makeFileOnly(final IOFileFilter filter) {
783        if (filter == null) {
784            return FileFileFilter.FILE;
785        }
786        return new AndFileFilter(FileFileFilter.FILE, filter);
787    }
788
789}