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 dir
067 * Files.<b>walkFileTree</b>(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 dir tree
074 * Files.<b>walkFileTree</b>(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 this
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 this
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 this
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     *
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}