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}