View Javadoc
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>"**&sol;*.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>"**&sol;.gitignore"</code>, <code>"**&sol;.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 }