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.api;
20
21 import java.nio.file.Path;
22 import java.nio.file.PathMatcher;
23 import java.util.Collection;
24 import java.util.List;
25 import java.util.Optional;
26
27 import org.apache.maven.api.annotations.Nonnull;
28
29 /**
30 * A root directory of source files.
31 * The sources may be Java main classes, test classes, resources or anything else identified by the scope.
32 *
33 * <h2>Default values</h2>
34 * The properties in this interface are defined in the {@code <Source>} element of the
35 * {@linkplain org.apache.maven.api.model.Model Maven project descriptor}.
36 * For each property, the default value is either empty or documented in the project descriptor.
37 */
38 public interface SourceRoot {
39 /**
40 * {@return the root directory where the sources are stored}
41 * The path is relative to the <abbr>POM</abbr> file.
42 *
43 * <h4>Default implementation</h4>
44 * The default value depends on whether a {@linkplain #module() module name} is specified in this source root:
45 * <ul>
46 * <li>
47 * If no module name, then the default directory is
48 * <code>src/{@linkplain #scope() scope}/{@linkplain #language() language}</code>.
49 * </li><li>
50 * If a module name is present, then the default directory is
51 * <code>src/{@linkplain #module() module}/{@linkplain #scope() scope}/{@linkplain #language() language}</code>.
52 * </li>
53 * </ul>
54 *
55 * The default value is relative.
56 * Implementation may override with absolute path instead.
57 */
58 default Path directory() {
59 Path src = Path.of("src");
60 return module().map(src::resolve)
61 .orElse(src)
62 .resolve(scope().id())
63 .resolve(language().id());
64 }
65
66 /**
67 * {@return the list of patterns for the files to include}
68 * The path separator is {@code /} on all platforms, including Windows.
69 * The prefix before the {@code :} character, if present and longer than 1 character, is the syntax.
70 * If no syntax is specified, or if its length is 1 character (interpreted as a Windows drive),
71 * the default is a Maven-specific variation of the {@code "glob"} pattern.
72 *
73 * <p>The default implementation returns an empty list, which means to apply a language-dependent pattern.
74 * For example, for the Java language, the pattern includes all files with the {@code .java} suffix.</p>
75 *
76 * @see java.nio.file.FileSystem#getPathMatcher(String)
77 */
78 default List<String> includes() {
79 return List.of();
80 }
81
82 /**
83 * {@return the list of patterns for the files to exclude}
84 * The exclusions are applied after the inclusions.
85 * The default implementation returns an empty list.
86 */
87 default List<String> excludes() {
88 return List.of();
89 }
90
91 /**
92 * {@return a matcher combining the include and exclude patterns}
93 * If the user did not specify any includes, the given {@code defaultIncludes} are used.
94 * These defaults depend on the plugin.
95 * For example, the default include of the Java compiler plugin is <code>"**/*.java"</code>.
96 *
97 * <p>If the user did not specify any excludes, the default is often files generated
98 * by Source Code Management (<abbr>SCM</abbr>) software or by the operating system.
99 * Examples: <code>"**/.gitignore"</code>, <code>"**/.DS_Store"</code>.</p>
100 *
101 * @param defaultIncludes the default includes if unspecified by the user
102 * @param useDefaultExcludes whether to add the default set of patterns to exclude,
103 * mostly Source Code Management (<abbr>SCM</abbr>) files
104 */
105 PathMatcher matcher(Collection<String> defaultIncludes, boolean useDefaultExcludes);
106
107 /**
108 * {@return in which context the source files will be used}
109 * Not to be confused with dependency scope.
110 * The default value is {@code "main"}.
111 *
112 * @see ProjectScope#MAIN
113 */
114 default ProjectScope scope() {
115 return ProjectScope.MAIN;
116 }
117
118 /**
119 * {@return the language of the source files}
120 * The default value is {@code "java"}.
121 *
122 * @see Language#JAVA_FAMILY
123 */
124 default Language language() {
125 return Language.JAVA_FAMILY;
126 }
127
128 /**
129 * {@return the name of the Java module (or other language-specific module) which is built by the sources}
130 * The default value is empty.
131 */
132 default Optional<String> module() {
133 return Optional.empty();
134 }
135
136 /**
137 * {@return the version of the platform where the code will be executed}
138 * In a Java environment, this is the value of the {@code --release} compiler option.
139 * The default value is empty.
140 */
141 default Optional<Version> targetVersion() {
142 return Optional.empty();
143 }
144
145 /**
146 * {@return an explicit target path, overriding the default value}
147 * <p>
148 * <strong>Important:</strong> This method returns the target path <em>as specified in the configuration</em>,
149 * which may be relative or absolute. It does <strong>not</strong> perform any path resolution.
150 * For the fully resolved absolute path, use {@link #targetPath(Project)} instead.
151 * </p>
152 * <p>
153 * <strong>Return Value Semantics:</strong>
154 * </p>
155 * <ul>
156 * <li><strong>Empty Optional</strong> - No explicit target path was specified. Files should be copied
157 * to the root of the output directory (see {@link Project#getOutputDirectory(ProjectScope)}).</li>
158 * <li><strong>Relative Path</strong> (e.g., {@code Path.of("META-INF/resources")}) - The path is
159 * <em>intended to be resolved</em> relative to the output directory for this source root's {@link #scope()}.
160 * <ul>
161 * <li>For {@link ProjectScope#MAIN}: relative to {@code target/classes}</li>
162 * <li>For {@link ProjectScope#TEST}: relative to {@code target/test-classes}</li>
163 * </ul>
164 * The actual resolution is performed by {@link #targetPath(Project)}.</li>
165 * <li><strong>Absolute Path</strong> (e.g., {@code Path.of("/tmp/custom")}) - The path is used as-is
166 * without any resolution. Files will be copied to this exact location.</li>
167 * </ul>
168 * <p>
169 * <strong>Maven 3 Compatibility:</strong> This behavior maintains compatibility with Maven 3.x,
170 * where resource {@code targetPath} elements were always interpreted as relative to the output directory
171 * ({@code project.build.outputDirectory} or {@code project.build.testOutputDirectory}),
172 * not the project base directory. Maven 3 plugins (like maven-resources-plugin) expect to receive
173 * the relative path and perform the resolution themselves.
174 * </p>
175 * <p>
176 * <strong>Effect on Module and Target Version:</strong>
177 * When a target path is explicitly specified, the values of {@link #module()} and {@link #targetVersion()}
178 * are not used for inferring the output path (they are still used as compiler options however).
179 * This means that for scripts and resources, the files below the path specified by {@link #directory()}
180 * are copied to the path specified by {@code targetPath()} with the exact same directory structure.
181 * </p>
182 * <p>
183 * <strong>Usage Guidance:</strong>
184 * </p>
185 * <ul>
186 * <li><strong>For Maven 4 API consumers:</strong> Use {@link #targetPath(Project)} to get the
187 * fully resolved absolute path where files should be copied.</li>
188 * <li><strong>For Maven 3 compatibility layer:</strong> Use this method to get the path as specified
189 * in the configuration, which can then be passed to legacy plugins that expect to perform
190 * their own resolution.</li>
191 * <li><strong>For implementers:</strong> Store the path exactly as provided in the configuration.
192 * Do not resolve relative paths at storage time.</li>
193 * </ul>
194 *
195 * @see #targetPath(Project)
196 * @see Project#getOutputDirectory(ProjectScope)
197 */
198 default Optional<Path> targetPath() {
199 return Optional.empty();
200 }
201
202 /**
203 * {@return the fully resolved absolute target path where files should be copied}
204 * <p>
205 * <strong>Purpose:</strong> This method performs the complete path resolution logic, converting
206 * the potentially relative {@link #targetPath()} into an absolute filesystem path. This is the
207 * method that Maven 4 API consumers should use when they need to know the actual destination
208 * directory for copying files.
209 * </p>
210 * <p>
211 * <strong>Resolution Algorithm:</strong>
212 * </p>
213 * <ol>
214 * <li>Obtain the {@linkplain #targetPath() configured target path} (which may be empty, relative, or absolute)</li>
215 * <li>If the configured target path is absolute (e.g., {@code /tmp/custom}):
216 * <ul><li>Return it unchanged (no resolution needed)</li></ul></li>
217 * <li>Otherwise, get the output directory for this source root's {@link #scope()} by calling
218 * {@code project.getOutputDirectory(scope())}:
219 * <ul>
220 * <li>For {@link ProjectScope#MAIN}: typically {@code /path/to/project/target/classes}</li>
221 * <li>For {@link ProjectScope#TEST}: typically {@code /path/to/project/target/test-classes}</li>
222 * </ul></li>
223 * <li>If the configured target path is empty:
224 * <ul><li>Return the output directory as-is</li></ul></li>
225 * <li>If the configured target path is relative (e.g., {@code META-INF/resources}):
226 * <ul><li>Resolve it against the output directory using {@code outputDirectory.resolve(targetPath)}</li></ul></li>
227 * </ol>
228 * <p>
229 * <strong>Concrete Examples:</strong>
230 * </p>
231 * <p>
232 * Given a project at {@code /home/user/myproject} with {@link ProjectScope#MAIN}:
233 * </p>
234 * <table class="striped">
235 * <caption>Target Path Resolution Examples</caption>
236 * <thead>
237 * <tr>
238 * <th>Configuration ({@code targetPath()})</th>
239 * <th>Output Directory</th>
240 * <th>Result ({@code targetPath(project)})</th>
241 * <th>Explanation</th>
242 * </tr>
243 * </thead>
244 * <tbody>
245 * <tr>
246 * <td>{@code Optional.empty()}</td>
247 * <td>{@code /home/user/myproject/target/classes}</td>
248 * <td>{@code /home/user/myproject/target/classes}</td>
249 * <td>No explicit path → use output directory</td>
250 * </tr>
251 * <tr>
252 * <td>{@code Optional.of(Path.of("META-INF"))}</td>
253 * <td>{@code /home/user/myproject/target/classes}</td>
254 * <td>{@code /home/user/myproject/target/classes/META-INF}</td>
255 * <td>Relative path → resolve against output directory</td>
256 * </tr>
257 * <tr>
258 * <td>{@code Optional.of(Path.of("WEB-INF/classes"))}</td>
259 * <td>{@code /home/user/myproject/target/classes}</td>
260 * <td>{@code /home/user/myproject/target/classes/WEB-INF/classes}</td>
261 * <td>Relative path with subdirectories</td>
262 * </tr>
263 * <tr>
264 * <td>{@code Optional.of(Path.of("/tmp/custom"))}</td>
265 * <td>{@code /home/user/myproject/target/classes}</td>
266 * <td>{@code /tmp/custom}</td>
267 * <td>Absolute path → use as-is (no resolution)</td>
268 * </tr>
269 * </tbody>
270 * </table>
271 * <p>
272 * <strong>Relationship to {@link #targetPath()}:</strong>
273 * </p>
274 * <p>
275 * This method is the <em>resolution</em> counterpart to {@link #targetPath()}, which is the
276 * <em>storage</em> method. While {@code targetPath()} returns the path as configured (potentially relative),
277 * this method returns the absolute path where files will actually be written. The separation allows:
278 * </p>
279 * <ul>
280 * <li>Maven 4 API consumers to get absolute paths via this method</li>
281 * <li>Maven 3 compatibility layer to get relative paths via {@code targetPath()} for legacy plugins</li>
282 * <li>Implementations to store paths without premature resolution</li>
283 * </ul>
284 * <p>
285 * <strong>Implementation Note:</strong> The default implementation is equivalent to:
286 * </p>
287 * <pre>{@code
288 * Optional<Path> configured = targetPath();
289 * if (configured.isPresent() && configured.get().isAbsolute()) {
290 * return configured.get();
291 * }
292 * Path outputDir = project.getOutputDirectory(scope());
293 * return configured.map(outputDir::resolve).orElse(outputDir);
294 * }</pre>
295 *
296 * @param project the project to use for obtaining the output directory
297 * @return the absolute path where files from {@link #directory()} should be copied
298 *
299 * @see #targetPath()
300 * @see Project#getOutputDirectory(ProjectScope)
301 */
302 @Nonnull
303 default Path targetPath(@Nonnull Project project) {
304 Optional<Path> targetPath = targetPath();
305 // The test for `isAbsolute()` is a small optimization for avoiding the call to `getOutputDirectory(…)`.
306 return targetPath.filter(Path::isAbsolute).orElseGet(() -> {
307 Path base = project.getOutputDirectory(scope());
308 return targetPath.map(base::resolve).orElse(base);
309 });
310 }
311
312 /**
313 * {@return whether resources are filtered to replace tokens with parameterized values}
314 * The default value is {@code false}.
315 */
316 default boolean stringFiltering() {
317 return false;
318 }
319
320 /**
321 * {@return whether the directory described by this source element should be included in the build}
322 * The default value is {@code true}.
323 */
324 default boolean enabled() {
325 return true;
326 }
327 }