    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 *      https://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(this);
    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    private WildcardFileFilter(final Builder builder) {
    171        this(builder.ioCase, builder.wildcards);
    172    }
    173
    174    /**
    175     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
    176     *
    177     * @param wildcards the array of wildcards to match, not null.
    178     * @param ioCase    how to handle case sensitivity, null means case-sensitive.
    179     * @throws NullPointerException if the pattern array is null.
    180     */
    181    private WildcardFileFilter(final IOCase ioCase, final String... wildcards) {
    182        this.wildcards = requireWildcards(wildcards).clone();
    183        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
    184    }
    185
    186    /**
    187     * Constructs a new case-sensitive wildcard filter for a list of wildcards.
    188     *
    189     * @param wildcards the list of wildcards to match, not null.
    190     * @throws IllegalArgumentException if the pattern list is null.
    191     * @throws ClassCastException       if the list does not contain Strings.
    192     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    193     */
    194    @Deprecated
    195    public WildcardFileFilter(final List<String> wildcards) {
    196        this(wildcards, IOCase.SENSITIVE);
    197    }
    198
    199    /**
    200     * Constructs a new wildcard filter for a list of wildcards specifying case-sensitivity.
    201     *
    202     * @param wildcards the list of wildcards to match, not null.
    203     * @param ioCase    how to handle case sensitivity, null means case-sensitive.
    204     * @throws IllegalArgumentException if the pattern list is null.
    205     * @throws ClassCastException       if the list does not contain Strings.
    206     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    207     */
    208    @Deprecated
    209    public WildcardFileFilter(final List<String> wildcards, final IOCase ioCase) {
    210        this(ioCase, requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
    211    }
    212
    213    /**
    214     * Constructs a new case-sensitive wildcard filter for a single wildcard.
    215     *
    216     * @param wildcard the wildcard to match.
    217     * @throws IllegalArgumentException if the pattern is null.
    218     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    219     */
    220    @Deprecated
    221    public WildcardFileFilter(final String wildcard) {
    222        this(IOCase.SENSITIVE, requireWildcards(wildcard));
    223    }
    224
    225    /**
    226     * Constructs a new case-sensitive wildcard filter for an array of wildcards.
    227     *
    228     * @param wildcards the array of wildcards to match.
    229     * @throws NullPointerException if the pattern array is null.
    230     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    231     */
    232    @Deprecated
    233    public WildcardFileFilter(final String... wildcards) {
    234        this(IOCase.SENSITIVE, wildcards);
    235    }
    236
    237    /**
    238     * Constructs a new wildcard filter for a single wildcard specifying case-sensitivity.
    239     *
    240     * @param wildcard the wildcard to match, not null.
    241     * @param ioCase   how to handle case sensitivity, null means case-sensitive.
    242     * @throws NullPointerException if the pattern is null.
    243     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    244     */
    245    @Deprecated
    246    public WildcardFileFilter(final String wildcard, final IOCase ioCase) {
    247        this(ioCase, wildcard);
    248    }
    249
    250    /**
    251     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
    252     *
    253     * @param wildcards the array of wildcards to match, not null.
    254     * @param ioCase    how to handle case sensitivity, null means case-sensitive.
    255     * @throws NullPointerException if the pattern array is null.
    256     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}.
    257     */
    258    @Deprecated
    259    public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) {
    260        this(ioCase, wildcards);
    261    }
    262
    263    /**
    264     * Tests to see if the file name matches one of the wildcards.
    265     *
    266     * @param file the file to check.
    267     * @return true if the file name matches one of the wildcards.
    268     */
    269    @Override
    270    public boolean accept(final File file) {
    271        return accept(file.getName());
    272    }
    273
    274    /**
    275     * Tests to see if the file name matches one of the wildcards.
    276     *
    277     * @param dir  the file directory (ignored).
    278     * @param name the file name.
    279     * @return true if the file name matches one of the wildcards.
    280     */
    281    @Override
    282    public boolean accept(final File dir, final String name) {
    283        return accept(name);
    284    }
    285
    286    /**
    287     * Tests to see if the file name matches one of the wildcards.
    288     *
    289     * @param path the file to check.
    290     * @param attributes the path's basic attributes (may be null).
    291     * @return true if the file name matches one of the wildcards.
    292     * @since 2.9.0
    293     */
    294    @Override
    295    public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) {
    296        return toFileVisitResult(accept(PathUtils.getFileNameString(path)));
    297    }
    298
    299    private boolean accept(final String name) {
    300        return Stream.of(wildcards).anyMatch(wildcard -> FilenameUtils.wildcardMatch(name, wildcard, ioCase));
    301    }
    302
    303    /**
    304     * Provide a String representation of this file filter.
    305     *
    306     * @return a String representation.
    307     */
    308    @Override
    309    public String toString() {
    310        final StringBuilder buffer = new StringBuilder();
    311        buffer.append(super.toString());
    312        buffer.append("(");
    313        append(wildcards, buffer);
    314        buffer.append(")");
    315        return buffer.toString();
    316    }
    317}