View Javadoc
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 }