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}