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 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(ioCase, wildcards);
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    /**
171     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
172     *
173     * @param wildcards the array of wildcards to match, not null
174     * @param ioCase    how to handle case sensitivity, null means case-sensitive
175     * @throws NullPointerException if the pattern array is null
176     */
177    private WildcardFileFilter(final IOCase ioCase, final String... wildcards) {
178        this.wildcards = requireWildcards(wildcards).clone();
179        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
180    }
181
182    /**
183     * Constructs a new case-sensitive wildcard filter for a list of wildcards.
184     *
185     * @param wildcards the list of wildcards to match, not null
186     * @throws IllegalArgumentException if the pattern list is null
187     * @throws ClassCastException       if the list does not contain Strings
188     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
189     */
190    @Deprecated
191    public WildcardFileFilter(final List<String> wildcards) {
192        this(wildcards, IOCase.SENSITIVE);
193    }
194
195    /**
196     * Constructs a new wildcard filter for a list of wildcards specifying case-sensitivity.
197     *
198     * @param wildcards the list of wildcards to match, not null
199     * @param ioCase    how to handle case sensitivity, null means case-sensitive
200     * @throws IllegalArgumentException if the pattern list is null
201     * @throws ClassCastException       if the list does not contain Strings
202     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
203     */
204    @Deprecated
205    public WildcardFileFilter(final List<String> wildcards, final IOCase ioCase) {
206        this(ioCase, requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
207    }
208
209    /**
210     * Constructs a new case-sensitive wildcard filter for a single wildcard.
211     *
212     * @param wildcard the wildcard to match
213     * @throws IllegalArgumentException if the pattern is null
214     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
215     */
216    @Deprecated
217    public WildcardFileFilter(final String wildcard) {
218        this(IOCase.SENSITIVE, requireWildcards(wildcard));
219    }
220
221    /**
222     * Constructs a new case-sensitive wildcard filter for an array of wildcards.
223     *
224     * @param wildcards the array of wildcards to match
225     * @throws NullPointerException if the pattern array is null
226     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
227     */
228    @Deprecated
229    public WildcardFileFilter(final String... wildcards) {
230        this(IOCase.SENSITIVE, wildcards);
231    }
232
233    /**
234     * Constructs a new wildcard filter for a single wildcard specifying case-sensitivity.
235     *
236     * @param wildcard the wildcard to match, not null
237     * @param ioCase   how to handle case sensitivity, null means case-sensitive
238     * @throws NullPointerException if the pattern is null
239     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
240     */
241    @Deprecated
242    public WildcardFileFilter(final String wildcard, final IOCase ioCase) {
243        this(ioCase, wildcard);
244    }
245
246    /**
247     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
248     *
249     * @param wildcards the array of wildcards to match, not null
250     * @param ioCase    how to handle case sensitivity, null means case-sensitive
251     * @throws NullPointerException if the pattern array is null
252     * @deprecated Use {@link #builder()}, {@link Builder}, and {@link Builder#get()}
253     */
254    @Deprecated
255    public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) {
256        this(ioCase, wildcards);
257    }
258
259    /**
260     * Checks to see if the file name matches one of the wildcards.
261     *
262     * @param file the file to check
263     * @return true if the file name matches one of the wildcards
264     */
265    @Override
266    public boolean accept(final File file) {
267        return accept(file.getName());
268    }
269
270    /**
271     * Checks to see if the file name matches one of the wildcards.
272     *
273     * @param dir  the file directory (ignored)
274     * @param name the file name
275     * @return true if the file name matches one of the wildcards
276     */
277    @Override
278    public boolean accept(final File dir, final String name) {
279        return accept(name);
280    }
281
282    /**
283     * Checks to see if the file name matches one of the wildcards.
284     *
285     * @param path the file to check
286     * @param attributes the path's basic attributes (may be null).
287     * @return true if the file name matches one of the wildcards.
288     * @since 2.9.0
289     */
290    @Override
291    public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) {
292        return toFileVisitResult(accept(PathUtils.getFileNameString(path)));
293    }
294
295    private boolean accept(final String name) {
296        return Stream.of(wildcards).anyMatch(wildcard -> FilenameUtils.wildcardMatch(name, wildcard, ioCase));
297    }
298
299    /**
300     * Provide a String representation of this file filter.
301     *
302     * @return a String representation
303     */
304    @Override
305    public String toString() {
306        final StringBuilder buffer = new StringBuilder();
307        buffer.append(super.toString());
308        buffer.append("(");
309        append(wildcards, buffer);
310        buffer.append(")");
311        return buffer.toString();
312    }
313}