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