    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        /**
    102         * Constructs a new builder of {@link WildcardFileFilter}.
    103         */
    104        public Builder() {
    105            // empty
    106        }
    107
    108        @Override
    109        public WildcardFileFilter get() {
    110            return new WildcardFileFilter(ioCase, wildcards);
    111        }
    112
    113        /**
    114         * Sets how to handle case sensitivity, null means case-sensitive.
    115         *
    116         * @param ioCase how to handle case sensitivity, null means case-sensitive.
    117         * @return {@code this} instance.
    118         */
    119        public Builder setIoCase(final IOCase ioCase) {
    120            this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
    121            return this;
    122        }
    123
    124        /**
    125         * Sets the list of wildcards to match, not null.
    126         *
    127         * @param wildcards the list of wildcards to match, not null.
    128         * @return {@code this} instance.
    129         */
    130        public Builder setWildcards(final List<String> wildcards) {
    131            setWildcards(requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
    132            return this;
    133        }
    134
    135        /**
    136         * Sets the wildcards to match, not null.
    137         *
    138         * @param wildcards the wildcards to match, not null.
    139         * @return {@code this} instance.
    140         */
    141        public Builder setWildcards(final String... wildcards) {
    142            this.wildcards = requireWildcards(wildcards);
    143            return this;
    144        }
    145
    146    }
    147
    148    private static final long serialVersionUID = -7426486598995782105L;
    149
    150    /**
    151     * Constructs a new {@link Builder}.
    152     *
    153     * @return a new {@link Builder}.
    154     * @since 2.12.0
    155     */
    156    public static Builder builder() {
    157        return new Builder();
    158    }
    159
    160    private static <T> T requireWildcards(final T wildcards) {
    161        return Objects.requireNonNull(wildcards, "wildcards");
    162    }
    163
    164    /** The wildcards that will be used to match file names. */
    165    private final String[] wildcards;
    166
    167    /** Whether the comparison is case-sensitive. */
    168    private final IOCase ioCase;
    169
    170    /**
    171     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
    172     *
    173     * @param wildcards the array of wildcards to match, not null
    174     * @param ioCase    how to handle case sensitivity, null means case-sensitive
    175     * @throws NullPointerException if the pattern array is null
    176     */
    177    private WildcardFileFilter(final IOCase ioCase, final String... wildcards) {
    178        this.wildcards = requireWildcards(wildcards).clone();
    179        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
    180    }
    181
    182    /**
    183     * Constructs a new case-sensitive wildcard filter for a list of wildcards.
    184     *
    185     * @param wildcards the list of wildcards to match, not null
    186     * @throws IllegalArgumentException if the pattern list is null
    187     * @throws ClassCastException       if the list does not contain Strings
    188     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    189     */
    190    @Deprecated
    191    public WildcardFileFilter(final List<String> wildcards) {
    192        this(wildcards, IOCase.SENSITIVE);
    193    }
    194
    195    /**
    196     * Constructs a new wildcard filter for a list of wildcards specifying case-sensitivity.
    197     *
    198     * @param wildcards the list of wildcards to match, not null
    199     * @param ioCase    how to handle case sensitivity, null means case-sensitive
    200     * @throws IllegalArgumentException if the pattern list is null
    201     * @throws ClassCastException       if the list does not contain Strings
    202     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    203     */
    204    @Deprecated
    205    public WildcardFileFilter(final List<String> wildcards, final IOCase ioCase) {
    206        this(ioCase, requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
    207    }
    208
    209    /**
    210     * Constructs a new case-sensitive wildcard filter for a single wildcard.
    211     *
    212     * @param wildcard the wildcard to match
    213     * @throws IllegalArgumentException if the pattern is null
    214     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    215     */
    216    @Deprecated
    217    public WildcardFileFilter(final String wildcard) {
    218        this(IOCase.SENSITIVE, requireWildcards(wildcard));
    219    }
    220
    221    /**
    222     * Constructs a new case-sensitive wildcard filter for an array of wildcards.
    223     *
    224     * @param wildcards the array of wildcards to match
    225     * @throws NullPointerException if the pattern array is null
    226     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    227     */
    228    @Deprecated
    229    public WildcardFileFilter(final String... wildcards) {
    230        this(IOCase.SENSITIVE, wildcards);
    231    }
    232
    233    /**
    234     * Constructs a new wildcard filter for a single wildcard specifying case-sensitivity.
    235     *
    236     * @param wildcard the wildcard to match, not null
    237     * @param ioCase   how to handle case sensitivity, null means case-sensitive
    238     * @throws NullPointerException if the pattern is null
    239     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    240     */
    241    @Deprecated
    242    public WildcardFileFilter(final String wildcard, final IOCase ioCase) {
    243        this(ioCase, wildcard);
    244    }
    245
    246    /**
    247     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
    248     *
    249     * @param wildcards the array of wildcards to match, not null
    250     * @param ioCase    how to handle case sensitivity, null means case-sensitive
    251     * @throws NullPointerException if the pattern array is null
    252     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
    253     */
    254    @Deprecated
    255    public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) {
    256        this(ioCase, wildcards);
    257    }
    258
    259    /**
    260     * Checks to see if the file name matches one of the wildcards.
    261     *
    262     * @param file the file to check
    263     * @return true if the file name matches one of the wildcards
    264     */
    265    @Override
    266    public boolean accept(final File file) {
    267        return accept(file.getName());
    268    }
    269
    270    /**
    271     * Checks to see if the file name matches one of the wildcards.
    272     *
    273     * @param dir  the file directory (ignored)
    274     * @param name the file name
    275     * @return true if the file name matches one of the wildcards
    276     */
    277    @Override
    278    public boolean accept(final File dir, final String name) {
    279        return accept(name);
    280    }
    281
    282    /**
    283     * Checks to see if the file name matches one of the wildcards.
    284     *
    285     * @param path the file to check
    286     * @param attributes the path's basic attributes (may be null).
    287     * @return true if the file name matches one of the wildcards.
    288     * @since 2.9.0
    289     */
    290    @Override
    291    public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) {
    292        return toFileVisitResult(accept(PathUtils.getFileNameString(path)));
    293    }
    294
    295    private boolean accept(final String name) {
    296        return Stream.of(wildcards).anyMatch(wildcard -> FilenameUtils.wildcardMatch(name, wildcard, ioCase));
    297    }
    298
    299    /**
    300     * Provide a String representation of this file filter.
    301     *
    302     * @return a String representation
    303     */
    304    @Override
    305    public String toString() {
    306        final StringBuilder buffer = new StringBuilder();
    307        buffer.append(super.toString());
    308        buffer.append("(");
    309        append(wildcards, buffer);
    310        buffer.append(")");
    311        return buffer.toString();
    312    }
    313}