1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.plugin.compiler;
20
21 import java.io.IOException;
22 import java.io.UncheckedIOException;
23 import java.nio.file.FileSystem;
24 import java.nio.file.FileVisitOption;
25 import java.nio.file.FileVisitResult;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.PathMatcher;
29 import java.nio.file.SimpleFileVisitor;
30 import java.nio.file.attribute.BasicFileAttributes;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.EnumSet;
35 import java.util.List;
36
37 /**
38 * Applies inclusion and exclusion filters on paths, and builds a list of files in a directory tree.
39 * The set of allowed syntax contains at least "glob" and "regex".
40 * See {@link FileSystem#getPathMatcher(String)} Javadoc for a description of the "glob" syntax.
41 * If no syntax is specified, then the default syntax is a derivative of the "glob" syntax which
42 * reproduces the behavior of Maven 3.
43 *
44 * <p>The list of files to process is built by applying the path matcher on each regular (non directory) files.
45 * The walk in file trees has the following characteristics:</p>
46 *
47 * <ul>
48 * <li>Symbolic links are followed.</li>
49 * <li>Hidden files and hidden directories are ignored.</li>
50 * </ul>
51 *
52 * Instances of this class can be reused for filtering many directories, but is not thread safe.
53 * Each instance shall be used by a single thread only.
54 *
55 * @author Martin Desruisseaux
56 */
57 final class PathFilter extends SimpleFileVisitor<Path> {
58 /**
59 * Whether to use the default include pattern.
60 * The pattern depends on the type of source file.
61 *
62 * @see javax.tools.JavaFileObject.Kind#extension
63 */
64 private final boolean useDefaultInclude;
65
66 /**
67 * Inclusion filters for the files in the directories to walk as specified in the plugin configuration.
68 * The array should contain at least one element. If {@link #useDefaultInclude} is {@code true}, then
69 * this array length shall be exactly 1 and the single element is overwritten for each directory to walk.
70 *
71 * @see SourceDirectory#includes
72 */
73 private final String[] includes;
74
75 /**
76 * Exclusion filters for the files in the directories to walk as specified in the plugin configuration.
77 *
78 * @see SourceDirectory#excludes
79 */
80 private final String[] excludes;
81
82 /**
83 * Combination of include and exclude filters. This is an instance of {@link PathSelector},
84 * unless the includes/excludes can be simplified to a single standard matcher instance.
85 */
86 private PathMatcher matchers;
87
88 /**
89 * All exclusion filters for incremental build calculation, or an empty list if none.
90 * Updated files, if excluded by a pattern, will not cause the project to be rebuilt.
91 */
92 private final Collection<String> incrementalExcludes;
93
94 /**
95 * The matchers for exclusion filters for incremental build calculation.
96 * May be an instance of {@link PathSelector}, or {@code null} if none.
97 */
98 private PathMatcher incrementalExcludeMatchers;
99
100 /**
101 * The result of listing all files, or {@code null} if no walking is in progress.
102 * This field is temporarily assigned a value when walking in a tree of directories,
103 * then reset to {@code null} after the walk finished.
104 */
105 private List<SourceFile> sourceFiles;
106
107 /**
108 * The root directory of files being scanned.
109 * This field is temporarily assigned a value when walking in a tree of directories,
110 * then reset to {@code null} after the walk finished.
111 */
112 private SourceDirectory sourceRoot;
113
114 /**
115 * Creates a new filter.
116 *
117 * @param mojo the <abbr>MOJO</abbr> from which to take the includes/excludes configuration
118 */
119 PathFilter(AbstractCompilerMojo mojo) {
120 Collection<String> specified = mojo.getIncludes();
121 useDefaultInclude = specified.isEmpty();
122 if (useDefaultInclude) {
123 specified = List.of("**"); // Place-holder replaced by "**/*.java" in `test(…)`.
124 }
125 includes = specified.toArray(String[]::new);
126 excludes = mojo.getExcludes().toArray(String[]::new);
127 incrementalExcludes = mojo.getIncrementalExcludes();
128 }
129
130 /**
131 * Invoked for a file in a directory. If the given file passes the include/exclude filters,
132 * then it is added to the list of source files.
133 *
134 * @param file the source file to test
135 * @param attrs the file basic attributes
136 * @return {@link FileVisitResult#CONTINUE}
137 */
138 @Override
139 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
140 if (matchers.matches(file)) {
141 sourceFiles.add(new SourceFile(
142 sourceRoot,
143 file,
144 attrs,
145 (incrementalExcludeMatchers != null) && incrementalExcludeMatchers.matches(file)));
146 }
147 return FileVisitResult.CONTINUE;
148 }
149
150 /**
151 * Invoked for a directory before entries in the directory are visited.
152 * If the directory is hidden, then it is skipped.
153 */
154 @Override
155 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
156 return Files.isHidden(dir) ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE;
157 }
158
159 /**
160 * {@return all source files found in the given root directories}
161 * The include and exclude filters specified at construction time are applied.
162 * Hidden directories are ignored, and symbolic links are followed.
163 *
164 * @param rootDirectories the root directories to scan
165 * @throws IOException if a root directory cannot be walked
166 */
167 public List<SourceFile> walkSourceFiles(Iterable<SourceDirectory> rootDirectories) throws IOException {
168 final var result = new ArrayList<SourceFile>();
169 try {
170 sourceFiles = result;
171 for (SourceDirectory directory : rootDirectories) {
172 if (!incrementalExcludes.isEmpty()) {
173 incrementalExcludeMatchers = new PathSelector(directory.root, incrementalExcludes, null).simplify();
174 }
175 String[] includesOrDefault = includes;
176 if (useDefaultInclude) {
177 if (directory.includes.isEmpty()) {
178 includesOrDefault[0] = "glob:**" + directory.fileKind.extension;
179 } else {
180 includesOrDefault = null;
181 }
182 }
183 sourceRoot = directory;
184 matchers = new PathSelector(
185 directory.root,
186 concat(directory.includes, includesOrDefault),
187 concat(directory.excludes, excludes))
188 .simplify();
189 Files.walkFileTree(directory.root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, this);
190 }
191 } catch (UncheckedIOException e) {
192 throw e.getCause();
193 } finally {
194 sourceRoot = null;
195 sourceFiles = null;
196 matchers = null;
197 }
198 return result;
199 }
200
201 /**
202 * Returns the concatenation of patterns specified in the source with the patterns specified in the plugin.
203 * As a side-effect, this method set the {@link #needRelativize} flag to {@code true} if at least one pattern
204 * does not start with {@code "**"}. The latter is a slight optimization for avoiding the need to relativize
205 * each path before to give it to a matcher when this relativization is not necessary.
206 *
207 * @param source the patterns specified in the {@code <source>} element
208 * @param plugin the patterns specified in the {@code <plugin>} element, or null if none
209 */
210 private static List<String> concat(List<String> source, String[] plugin) {
211 if (plugin == null || plugin.length == 0) {
212 return source;
213 }
214 var patterns = new ArrayList<String>(source);
215 patterns.addAll(Arrays.asList(plugin));
216 return patterns;
217 }
218 }