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}