1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.io.filefilter;
18
19 import java.io.File;
20 import java.io.Serializable;
21 import java.nio.file.FileVisitResult;
22 import java.nio.file.Path;
23 import java.nio.file.attribute.BasicFileAttributes;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.stream.Stream;
27
28 import org.apache.commons.io.FilenameUtils;
29 import org.apache.commons.io.IOCase;
30 import org.apache.commons.io.build.AbstractSupplier;
31 import org.apache.commons.io.file.PathUtils;
32
33 /**
34 * Filters files using the supplied wildcards.
35 * <p>
36 * This filter selects files and directories based on one or more wildcards. Testing is case-sensitive by default, but this can be configured.
37 * </p>
38 * <p>
39 * 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
40 * command lines. The check is case-sensitive by default. See {@link FilenameUtils#wildcardMatchOnSystem(String,String)} for more information.
41 * </p>
42 * <p>
43 * To build an instance, use {@link Builder}.
44 * </p>
45 * <p>
46 * For example:
47 * </p>
48 * <h2>Using Classic IO</h2>
49 *
50 * <pre>
51 * File dir = FileUtils.current();
52 * FileFilter fileFilter = WildcardFileFilter.builder().setWildcards("*test*.java~*~").get();
53 * File[] files = dir.listFiles(fileFilter);
54 * for (String file : files) {
55 * System.out.println(file);
56 * }
57 * </pre>
58 *
59 * <h2>Using NIO</h2>
60 *
61 * <pre>
62 * final Path dir = PathUtils.current();
63 * final AccumulatorPathVisitor visitor = AccumulatorPathVisitor.withLongCounters(
64 * WildcardFileFilter.builder().setWildcards("*test*.java~*~").get());
65 * //
66 * // Walk one directory
67 * Files.<strong>walkFileTree</strong>(dir, Collections.emptySet(), 1, visitor);
68 * System.out.println(visitor.getPathCounters());
69 * System.out.println(visitor.getFileList());
70 * //
71 * visitor.getPathCounters().reset();
72 * //
73 * // Walk directory tree
74 * Files.<strong>walkFileTree</strong>(dir, visitor);
75 * System.out.println(visitor.getPathCounters());
76 * System.out.println(visitor.getDirList());
77 * System.out.println(visitor.getFileList());
78 * </pre>
79 * <h2>Deprecating Serialization</h2>
80 * <p>
81 * <em>Serialization is deprecated and will be removed in 3.0.</em>
82 * </p>
83 *
84 * @since 1.3
85 */
86 public class WildcardFileFilter extends AbstractFileFilter implements Serializable {
87
88 /**
89 * Builds a new {@link WildcardFileFilter} instance.
90 *
91 * @since 2.12.0
92 */
93 public static class Builder extends AbstractSupplier<WildcardFileFilter, Builder> {
94
95 /** The wildcards that will be used to match file names. */
96 private String[] wildcards;
97
98 /** Whether the comparison is case-sensitive. */
99 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 }