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.Objects;
025import java.util.function.Function;
026import java.util.regex.Pattern;
027
028import org.apache.commons.io.IOCase;
029import org.apache.commons.io.file.PathUtils;
030
031/**
032 * Filters files using supplied regular expression(s).
033 * <p>
034 * See java.util.regex.Pattern for regex matching rules.
035 * </p>
036 * <h2>Using Classic IO</h2>
037 * <p>
038 * e.g.
039 *
040 * <pre>
041 * File dir = FileUtils.current();
042 * FileFilter fileFilter = new RegexFileFilter("^.*[tT]est(-\\d+)?\\.java$");
043 * File[] files = dir.listFiles(fileFilter);
044 * for (String file : files) {
045 *     System.out.println(file);
046 * }
047 * </pre>
048 *
049 * <h2>Using NIO</h2>
050 *
051 * <pre>
052 * final Path dir = PathUtils.current();
053 * final AccumulatorPathVisitor visitor = AccumulatorPathVisitor.withLongCounters(new RegexFileFilter("^.*[tT]est(-\\d+)?\\.java$"));
054 * //
055 * // Walk one dir
056 * Files.<b>walkFileTree</b>(dir, Collections.emptySet(), 1, visitor);
057 * System.out.println(visitor.getPathCounters());
058 * System.out.println(visitor.getFileList());
059 * //
060 * visitor.getPathCounters().reset();
061 * //
062 * // Walk dir tree
063 * Files.<b>walkFileTree</b>(dir, visitor);
064 * System.out.println(visitor.getPathCounters());
065 * System.out.println(visitor.getDirList());
066 * System.out.println(visitor.getFileList());
067 * </pre>
068 * <h2>Deprecating Serialization</h2>
069 * <p>
070 * <em>Serialization is deprecated and will be removed in 3.0.</em>
071 * </p>
072 *
073 * @since 1.4
074 */
075public class RegexFileFilter extends AbstractFileFilter implements Serializable {
076
077    private static final long serialVersionUID = 4269646126155225062L;
078
079    /**
080     * Compiles the given pattern source.
081     *
082     * @param pattern the source pattern.
083     * @param flags the compilation flags.
084     * @return a new Pattern.
085     */
086    private static Pattern compile(final String pattern, final int flags) {
087        Objects.requireNonNull(pattern, "pattern");
088        return Pattern.compile(pattern, flags);
089    }
090
091    /**
092     * Converts IOCase to Pattern compilation flags.
093     *
094     * @param ioCase case-sensitivity.
095     * @return Pattern compilation flags.
096     */
097    private static int toFlags(final IOCase ioCase) {
098        return IOCase.isCaseSensitive(ioCase) ? 0 : Pattern.CASE_INSENSITIVE;
099    }
100
101    /** The regular expression pattern that will be used to match file names. */
102    private final Pattern pattern;
103
104    /** How convert a path to a string. */
105    private transient final Function<Path, String> pathToString;
106
107    /**
108     * Constructs a new regular expression filter for a compiled regular expression
109     *
110     * @param pattern regular expression to match.
111     * @throws NullPointerException if the pattern is null.
112     */
113    @SuppressWarnings("unchecked")
114    public RegexFileFilter(final Pattern pattern) {
115        this(pattern, (Function<Path, String> & Serializable) PathUtils::getFileNameString);
116    }
117
118    /**
119     * Constructs a new regular expression filter for a compiled regular expression
120     *
121     * @param pattern regular expression to match.
122     * @param pathToString How convert a path to a string.
123     * @throws NullPointerException if the pattern is null.
124     * @since 2.10.0
125     */
126    public RegexFileFilter(final Pattern pattern, final Function<Path, String> pathToString) {
127        Objects.requireNonNull(pattern, "pattern");
128        this.pattern = pattern;
129        this.pathToString = pathToString != null ? pathToString : Objects::toString;
130    }
131
132    /**
133     * Constructs a new regular expression filter.
134     *
135     * @param pattern regular string expression to match
136     * @throws NullPointerException if the pattern is null
137     */
138    public RegexFileFilter(final String pattern) {
139        this(pattern, 0);
140    }
141
142    /**
143     * Constructs a new regular expression filter with the specified flags.
144     *
145     * @param pattern regular string expression to match
146     * @param flags pattern flags - e.g. {@link Pattern#CASE_INSENSITIVE}
147     * @throws IllegalArgumentException if the pattern is null
148     */
149    public RegexFileFilter(final String pattern, final int flags) {
150        this(compile(pattern, flags));
151    }
152
153    /**
154     * Constructs a new regular expression filter with the specified flags case sensitivity.
155     *
156     * @param pattern regular string expression to match
157     * @param ioCase how to handle case sensitivity, null means case-sensitive
158     * @throws IllegalArgumentException if the pattern is null
159     */
160    public RegexFileFilter(final String pattern, final IOCase ioCase) {
161        this(compile(pattern, toFlags(ioCase)));
162    }
163
164    /**
165     * Checks to see if the file name matches one of the regular expressions.
166     *
167     * @param dir the file directory (ignored)
168     * @param name the file name
169     * @return true if the file name matches one of the regular expressions
170     */
171    @Override
172    public boolean accept(final File dir, final String name) {
173        return pattern.matcher(name).matches();
174    }
175
176    /**
177     * Checks to see if the file name matches one of the regular expressions.
178     *
179     * @param path the path
180     * @param attributes the path attributes
181     * @return true if the file name matches one of the regular expressions
182     */
183    @Override
184    public FileVisitResult accept(final Path path, final BasicFileAttributes attributes) {
185        final String result = pathToString.apply(path);
186        return toFileVisitResult(result != null && pattern.matcher(result).matches());
187    }
188
189    /**
190     * Returns a debug string.
191     *
192     * @since 2.10.0
193     */
194    @Override
195    public String toString() {
196        return "RegexFileFilter [pattern=" + pattern + "]";
197    }
198
199}