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