    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.Serializable;
    021import java.nio.file.FileVisitResult;
    022import java.nio.file.Path;
    023import java.nio.file.attribute.BasicFileAttributes;
    024import java.util.List;
    025import java.util.Objects;
    026import java.util.stream.Stream;
    027
    028import org.apache.commons.io.FilenameUtils;
    029import org.apache.commons.io.IOCase;
    030import org.apache.commons.io.build.AbstractSupplier;
    031import org.apache.commons.io.file.PathUtils;
    032
    033/**
    034 * Filters files using the supplied wildcards.
    035 * <p>
    036 * This filter selects files and directories based on one or more wildcards. Testing is case-sensitive by default, but this can be configured.
    037 * </p>
    038 * <p>
    039 * The wildcard matcher uses the characters '?' and '*' to represent a single or multiple wildcard characters. This is the same as often found on DOS/Unix
    040 * command lines. The check is case-sensitive by default. See {@link FilenameUtils#wildcardMatchOnSystem(String,String)} for more information.
    041 * </p>
    042 * <p>
    043 * To build an instance, use {@link Builder}.
    044 * </p>
    045 * <p>
    046 * For example:
    047 * </p>
    048 * <h2>Using Classic IO</h2>
    049 *
    050 * <pre>
    051 * File dir = FileUtils.current();
    052 * FileFilter fileFilter = WildcardFileFilter.builder().setWildcards("*test*.java~*~").get();
    053 * File[] files = dir.listFiles(fileFilter);
    054 * for (String file : files) {
    055 *     System.out.println(file);
    056 * }
    057 * </pre>
    058 *
    059 * <h2>Using NIO</h2>
    060 *
    061 * <pre>
    062 * final Path dir = PathUtils.current();
    063 * final AccumulatorPathVisitor visitor = AccumulatorPathVisitor.withLongCounters(
    064 *     WildcardFileFilter.builder().setWildcards("*test*.java~*~").get());
    065 * //
    066 * // Walk one directory
    067 * Files.<strong>walkFileTree</strong>(dir, Collections.emptySet(), 1, visitor);
    068 * System.out.println(visitor.getPathCounters());
    069 * System.out.println(visitor.getFileList());
    070 * //
    071 * visitor.getPathCounters().reset();
    072 * //
    073 * // Walk directory tree
    074 * Files.<strong>walkFileTree</strong>(dir, visitor);
    075 * System.out.println(visitor.getPathCounters());
    076 * System.out.println(visitor.getDirList());
    077 * System.out.println(visitor.getFileList());
    078 * </pre>
    079 * <h2>Deprecating Serialization</h2>
    080 * <p>
    081 * <em>Serialization is deprecated and will be removed in 3.0.</em>
    082 * </p>
    083 *
    084 * @since 1.3
    085 */
    086public class WildcardFileFilter extends AbstractFileFilter implements Serializable {
    087
    088    /**
    089     * Builds a new {@link WildcardFileFilter} instance.
    090     *
    091     * @since 2.12.0
    092     */
    093    public static class Builder extends AbstractSupplier<WildcardFileFilter, Builder> {
    094
    095        /** The wildcards that will be used to match file names. */
    096        private String[] wildcards;
    097
    098        /** Whether the comparison is case-sensitive. */
    099        private IOCase ioCase = IOCase.SENSITIVE;
    100
    101        @Override
    102        public WildcardFileFilter get() {
    103            return new WildcardFileFilter(ioCase, wildcards);
    104        }
    105
    106        /**
    107         * Sets how to handle case sensitivity, null means case-sensitive.
    108         *
    109         * @param ioCase how to handle case sensitivity, null means case-sensitive.
    110         * @return {@code this} instance.
    111         */
    112        public Builder setIoCase(final IOCase ioCase) {
    113            this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
    114            return this;
    115        }
    116
    117        /**
    118         * Sets the list of wildcards to match, not null.
    119         *
    120         * @param wildcards the list of wildcards to match, not null.
    121         * @return {@code this} instance.
    122         */
    123        public Builder setWildcards(final List<String> wildcards) {
    124            setWildcards(requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
    125            return this;
    126        }
    127
    128        /**
    129         * Sets the wildcards to match, not null.
    130         *
    131         * @param wildcards the wildcards to match, not null.
    132         * @return {@code this} instance.
    133         */
    134        public Builder setWildcards(final String... wildcards) {
    135            this.wildcards = requireWildcards(wildcards);
    136            return this;
    137        }
    138
    139    }
    140
    141    private static final long serialVersionUID = -7426486598995782105L;
    142
    143    /**
    144     * Constructs a new {@link Builder}.
    145     *
    146     * @return a new {@link Builder}.
    147     * @since 2.12.0
    148     */
    149    public static Builder builder() {
    150        return new Builder();
    151    }
    152
    153    private static <T> T requireWildcards(final T wildcards) {
    154        return Objects.requireNonNull(wildcards, "wildcards");
    155    }
    156
    157    /** The wildcards that will be used to match file names. */
    158    private final String[] wildcards;
    159
    160    /** Whether the comparison is case-sensitive. */
    161    private final IOCase ioCase;
    162
    163    /**
    164     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
    165     *
    166     * @param wildcards the array of wildcards to match, not null
    167     * @param ioCase    how to handle case sensitivity, null means case-sensitive
    168     * @throws NullPointerException if the pattern array is null
    169     */
    170    private WildcardFileFilter(final IOCase ioCase, final String... wildcards) {
    171        this.wildcards = requireWildcards(wildcards).clone();
    172        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
    173    }
    174
    175    /**
    176     * Constructs a new case-sensitive wildcard filter for a list of wildcards.
    177     *
    178     * @param wildcards the list of wildcards to match, not null
    179     * @throws IllegalArgumentException if the pattern list is null
    180     * @throws ClassCastException       if the list does not contain Strings
    181     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    182     */
    183    @Deprecated
    184    public WildcardFileFilter(final List<String> wildcards) {
    185        this(wildcards, IOCase.SENSITIVE);
    186    }
    187
    188    /**
    189     * Constructs a new wildcard filter for a list of wildcards specifying case-sensitivity.
    190     *
    191     * @param wildcards the list of wildcards to match, not null
    192     * @param ioCase    how to handle case sensitivity, null means case-sensitive
    193     * @throws IllegalArgumentException if the pattern list is null
    194     * @throws ClassCastException       if the list does not contain Strings
    195     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    196     */
    197    @Deprecated
    198    public WildcardFileFilter(final List<String> wildcards, final IOCase ioCase) {
    199        this(ioCase, requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
    200    }
    201
    202    /**
    203     * Constructs a new case-sensitive wildcard filter for a single wildcard.
    204     *
    205     * @param wildcard the wildcard to match
    206     * @throws IllegalArgumentException if the pattern is null
    207     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    208     */
    209    @Deprecated
    210    public WildcardFileFilter(final String wildcard) {
    211        this(IOCase.SENSITIVE, requireWildcards(wildcard));
    212    }
    213
    214    /**
    215     * Constructs a new case-sensitive wildcard filter for an array of wildcards.
    216     *
    217     * @param wildcards the array of wildcards to match
    218     * @throws NullPointerException if the pattern array is null
    219     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    220     */
    221    @Deprecated
    222    public WildcardFileFilter(final String... wildcards) {
    223        this(IOCase.SENSITIVE, wildcards);
    224    }
    225
    226    /**
    227     * Constructs a new wildcard filter for a single wildcard specifying case-sensitivity.
    228     *
    229     * @param wildcard the wildcard to match, not null
    230     * @param ioCase   how to handle case sensitivity, null means case-sensitive
    231     * @throws NullPointerException if the pattern is null
    232     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    233     */
    234    @Deprecated
    235    public WildcardFileFilter(final String wildcard, final IOCase ioCase) {
    236        this(ioCase, wildcard);
    237    }
    238
    239    /**
    240     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
    241     *
    242     * @param wildcards the array of wildcards to match, not null
    243     * @param ioCase    how to handle case sensitivity, null means case-sensitive
    244     * @throws NullPointerException if the pattern array is null
    245     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    246     */
    247    @Deprecated
    248    public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) {
    249        this(ioCase, wildcards);
    250    }
    251
    252    /**
    253     * Checks to see if the file name matches one of the wildcards.
    254     *
    255     * @param file the file to check
    256     * @return true if the file name matches one of the wildcards
    257     */
    258    @Override
    259    public boolean accept(final File file) {
    260        return accept(file.getName());
    261    }
    262
    263    /**
    264     * Checks to see if the file name matches one of the wildcards.
    265     *
    266     * @param dir  the file directory (ignored)
    267     * @param name the file name
    268     * @return true if the file name matches one of the wildcards
    269     */
    270    @Override
    271    public boolean accept(final File dir, final String name) {
    272        return accept(name);
    273    }
    274
    275    /**
    276     * Checks to see if the file name matches one of the wildcards.
    277     *
    278     * @param path the file to check
    279     * @param attributes the path's basic attributes (may be null).
    280     * @return true if the file name matches one of the wildcards.
    281     * @since 2.9.0
    282     */
    283    @Override
    284    public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) {
    285        return toFileVisitResult(accept(PathUtils.getFileNameString(path)));
    286    }
    287
    288    private boolean accept(final String name) {
    289        return Stream.of(wildcards).anyMatch(wildcard -> FilenameUtils.wildcardMatch(name, wildcard, ioCase));
    290    }
    291
    292    /**
    293     * Provide a String representation of this file filter.
    294     *
    295     * @return a String representation
    296     */
    297    @Override
    298    public String toString() {
    299        final StringBuilder buffer = new StringBuilder();
    300        buffer.append(super.toString());
    301        buffer.append("(");
    302        append(wildcards, buffer);
    303        buffer.append(")");
    304        return buffer.toString();
    305    }
    306}