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;
026
027import org.apache.commons.io.FilenameUtils;
028import org.apache.commons.io.IOCase;
029
030/**
031 * Filters files using the supplied wildcards.
032 * <p>
033 * This filter selects files and directories based on one or more wildcards.
034 * Testing is case-sensitive by default, but this can be configured.
035 * </p>
036 * <p>
037 * The wildcard matcher uses the characters '?' and '*' to represent a
038 * single or multiple wildcard characters.
039 * This is the same as often found on Dos/Unix command lines.
040 * The check is case-sensitive by default.
041 * See {@link FilenameUtils#wildcardMatchOnSystem(String,String)} for more information.
042 * </p>
043 * <p>
044 * For example:
045 * </p>
046 * <h2>Using Classic IO</h2>
047 * <pre>
048 * File dir = new File(".");
049 * FileFilter fileFilter = new WildcardFileFilter("*test*.java~*~");
050 * File[] files = dir.listFiles(fileFilter);
051 * for (String file : files) {
052 *     System.out.println(file);
053 * }
054 * </pre>
055 *
056 * <h2>Using NIO</h2>
057 * <pre>
058 * final Path dir = Paths.get("");
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 *
075 * @since 1.3
076 */
077public class WildcardFileFilter extends AbstractFileFilter implements Serializable {
078
079    private static final long serialVersionUID = -7426486598995782105L;
080
081    /** The wildcards that will be used to match file names. */
082    private final String[] wildcards;
083
084    /** Whether the comparison is case sensitive. */
085    private final IOCase caseSensitivity;
086
087    /**
088     * Construct a new case-sensitive wildcard filter for a list of wildcards.
089     *
090     * @param wildcards  the list of wildcards to match, not null
091     * @throws IllegalArgumentException if the pattern list is null
092     * @throws ClassCastException if the list does not contain Strings
093     */
094    public WildcardFileFilter(final List<String> wildcards) {
095        this(wildcards, IOCase.SENSITIVE);
096    }
097
098    /**
099     * Construct a new wildcard filter for a list of wildcards specifying case-sensitivity.
100     *
101     * @param wildcards  the list of wildcards to match, not null
102     * @param caseSensitivity  how to handle case sensitivity, null means case-sensitive
103     * @throws IllegalArgumentException if the pattern list is null
104     * @throws ClassCastException if the list does not contain Strings
105     */
106    public WildcardFileFilter(final List<String> wildcards, final IOCase caseSensitivity) {
107        if (wildcards == null) {
108            throw new IllegalArgumentException("The wildcard list must not be null");
109        }
110        this.wildcards = wildcards.toArray(EMPTY_STRING_ARRAY);
111        this.caseSensitivity = caseSensitivity == null ? IOCase.SENSITIVE : caseSensitivity;
112    }
113
114    /**
115     * Construct a new case-sensitive wildcard filter for a single wildcard.
116     *
117     * @param wildcard  the wildcard to match
118     * @throws IllegalArgumentException if the pattern is null
119     */
120    public WildcardFileFilter(final String wildcard) {
121        this(wildcard, IOCase.SENSITIVE);
122    }
123
124    /**
125     * Construct a new case-sensitive wildcard filter for an array of wildcards.
126     * <p>
127     *
128     * @param wildcards  the array of wildcards to match
129     * @throws IllegalArgumentException if the pattern array is null
130     */
131    public WildcardFileFilter(final String... wildcards) {
132        this(wildcards, IOCase.SENSITIVE);
133    }
134
135    /**
136     * Construct a new wildcard filter for a single wildcard specifying case-sensitivity.
137     *
138     * @param wildcard  the wildcard to match, not null
139     * @param caseSensitivity  how to handle case sensitivity, null means case-sensitive
140     * @throws IllegalArgumentException if the pattern is null
141     */
142    public WildcardFileFilter(final String wildcard, final IOCase caseSensitivity) {
143        if (wildcard == null) {
144            throw new IllegalArgumentException("The wildcard must not be null");
145        }
146        this.wildcards = new String[] { wildcard };
147        this.caseSensitivity = caseSensitivity == null ? IOCase.SENSITIVE : caseSensitivity;
148    }
149
150    /**
151     * Construct a new wildcard filter for an array of wildcards specifying case-sensitivity.
152     * <p>
153     *
154     * @param wildcards  the array of wildcards to match, not null
155     * @param caseSensitivity  how to handle case sensitivity, null means case-sensitive
156     * @throws IllegalArgumentException if the pattern array is null
157     */
158    public WildcardFileFilter(final String[] wildcards, final IOCase caseSensitivity) {
159        if (wildcards == null) {
160            throw new IllegalArgumentException("The wildcard array must not be null");
161        }
162        this.wildcards = new String[wildcards.length];
163        System.arraycopy(wildcards, 0, this.wildcards, 0, wildcards.length);
164        this.caseSensitivity = caseSensitivity == null ? IOCase.SENSITIVE : caseSensitivity;
165    }
166
167    /**
168     * Checks to see if the file name matches one of the wildcards.
169     *
170     * @param file  the file to check
171     * @return true if the file name matches one of the wildcards
172     */
173    @Override
174    public boolean accept(final File file) {
175        return accept(file.getName());
176    }
177
178    /**
179     * Checks to see if the file name matches one of the wildcards.
180     *
181     * @param dir  the file directory (ignored)
182     * @param name  the file name
183     * @return true if the file name matches one of the wildcards
184     */
185    @Override
186    public boolean accept(final File dir, final String name) {
187        return accept(name);
188    }
189
190    /**
191     * Checks to see if the file name matches one of the wildcards.
192     * @param file  the file to check
193     *
194     * @return true if the file name matches one of the wildcards.
195     * @since 2.9.0
196     */
197    @Override
198    public FileVisitResult accept(final Path file, final BasicFileAttributes attributes) {
199        return toFileVisitResult(accept(Objects.toString(file.getFileName(), null)), file);
200    }
201
202    private boolean accept(final String name) {
203        for (final String wildcard : wildcards) {
204            if (FilenameUtils.wildcardMatch(name, wildcard, caseSensitivity)) {
205                return true;
206            }
207        }
208        return false;
209    }
210
211    /**
212     * Provide a String representation of this file filter.
213     *
214     * @return a String representation
215     */
216    @Override
217    public String toString() {
218        final StringBuilder buffer = new StringBuilder();
219        buffer.append(super.toString());
220        buffer.append("(");
221        for (int i = 0; i < wildcards.length; i++) {
222            if (i > 0) {
223                buffer.append(",");
224            }
225            buffer.append(wildcards[i]);
226        }
227        buffer.append(")");
228        return buffer.toString();
229    }
230
231}