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.impl;
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.Objects;
26  import java.util.Optional;
27  
28  import org.apache.maven.api.Language;
29  import org.apache.maven.api.ProjectScope;
30  import org.apache.maven.api.Session;
31  import org.apache.maven.api.SourceRoot;
32  import org.apache.maven.api.Version;
33  import org.apache.maven.api.model.Resource;
34  import org.apache.maven.api.model.Source;
35  
36  /**
37   * A default implementation of {@code SourceRoot} built from the model.
38   */
39  public final class DefaultSourceRoot implements SourceRoot {
40      private final Path directory;
41  
42      private final List<String> includes;
43  
44      private final List<String> excludes;
45  
46      private final ProjectScope scope;
47  
48      private final Language language;
49  
50      private final String moduleName;
51  
52      private final Version targetVersion;
53  
54      private final Path targetPath;
55  
56      private final boolean stringFiltering;
57  
58      private final boolean enabled;
59  
60      /**
61       * Creates a new instance from the given model.
62       *
63       * @param session the session of resolving extensible enumerations
64       * @param baseDir the base directory for resolving relative paths
65       * @param source a source element from the model
66       */
67      public DefaultSourceRoot(final Session session, final Path baseDir, final Source source) {
68          includes = source.getIncludes();
69          excludes = source.getExcludes();
70          stringFiltering = source.isStringFiltering();
71          enabled = source.isEnabled();
72          moduleName = nonBlank(source.getModule());
73  
74          String value = nonBlank(source.getScope());
75          scope = (value != null) ? session.requireProjectScope(value) : ProjectScope.MAIN;
76  
77          value = nonBlank(source.getLang());
78          language = (value != null) ? session.requireLanguage(value) : Language.JAVA_FAMILY;
79  
80          value = nonBlank(source.getDirectory());
81          if (value != null) {
82              directory = baseDir.resolve(value);
83          } else {
84              Path src = baseDir.resolve("src");
85              if (moduleName != null) {
86                  src = src.resolve(language.id());
87              }
88              directory = src.resolve(scope.id()).resolve(language.id());
89          }
90  
91          value = nonBlank(source.getTargetVersion());
92          targetVersion = (value != null) ? session.parseVersion(value) : null;
93  
94          value = nonBlank(source.getTargetPath());
95          targetPath = (value != null) ? baseDir.resolve(value) : null;
96      }
97  
98      /**
99       * Creates a new instance from the given resource.
100      * This is used for migration from the previous way of declaring resources.
101      *
102      * @param baseDir the base directory for resolving relative paths
103      * @param scope the scope of the resource (main or test)
104      * @param resource a resource element from the model
105      */
106     public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource) {
107         String value = nonBlank(resource.getDirectory());
108         if (value == null) {
109             throw new IllegalArgumentException("Source declaration without directory value.");
110         }
111         directory = baseDir.resolve(value).normalize();
112         includes = resource.getIncludes();
113         excludes = resource.getExcludes();
114         stringFiltering = Boolean.parseBoolean(resource.getFiltering());
115         enabled = true;
116         moduleName = null;
117         this.scope = scope;
118         language = Language.RESOURCES;
119         targetVersion = null;
120         targetPath = null;
121     }
122 
123     /**
124      * Creates a new instance for the given directory and scope.
125      *
126      * @param scope scope of source code (main or test)
127      * @param language language of the source code
128      * @param directory directory of the source code
129      */
130     public DefaultSourceRoot(final ProjectScope scope, final Language language, final Path directory) {
131         this.scope = Objects.requireNonNull(scope);
132         this.language = Objects.requireNonNull(language);
133         this.directory = Objects.requireNonNull(directory);
134         includes = List.of();
135         excludes = List.of();
136         moduleName = null;
137         targetVersion = null;
138         targetPath = null;
139         stringFiltering = false;
140         enabled = true;
141     }
142 
143     /**
144      * Creates a new instance for the given directory and scope.
145      *
146      * @param scope scope of source code (main or test)
147      * @param language language of the source code
148      * @param directory directory of the source code
149      * @param includes patterns for the files to include, or {@code null} or empty if unspecified
150      * @param excludes patterns for the files to exclude, or {@code null} or empty if nothing to exclude
151      */
152     public DefaultSourceRoot(
153             final ProjectScope scope,
154             final Language language,
155             final Path directory,
156             List<String> includes,
157             List<String> excludes) {
158         this.scope = Objects.requireNonNull(scope);
159         this.language = language;
160         this.directory = Objects.requireNonNull(directory);
161         this.includes = includes != null ? List.copyOf(includes) : List.of();
162         this.excludes = excludes != null ? List.copyOf(excludes) : List.of();
163         moduleName = null;
164         targetVersion = null;
165         targetPath = null;
166         stringFiltering = false;
167         enabled = true;
168     }
169 
170     /**
171      * {@return the given value as a trimmed non-blank string, or null otherwise}.
172      */
173     private static String nonBlank(String value) {
174         if (value != null) {
175             value = value.trim();
176             if (value.isBlank()) {
177                 value = null;
178             }
179         }
180         return value;
181     }
182 
183     /**
184      * {@return the root directory where the sources are stored}.
185      */
186     @Override
187     public Path directory() {
188         return directory;
189     }
190 
191     /**
192      * {@return the patterns for the files to include}.
193      */
194     @Override
195     @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
196     public List<String> includes() {
197         return includes;
198     }
199 
200     /**
201      * {@return the patterns for the files to exclude}.
202      */
203     @Override
204     @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
205     public List<String> excludes() {
206         return excludes;
207     }
208 
209     /**
210      * {@return a matcher combining the include and exclude patterns}.
211      *
212      * @param defaultIncludes the default includes if unspecified by the user
213      * @param useDefaultExcludes whether to add the default set of patterns to exclude,
214      *        mostly Source Code Management (<abbr>SCM</abbr>) files
215      */
216     @Override
217     public PathMatcher matcher(Collection<String> defaultIncludes, boolean useDefaultExcludes) {
218         Collection<String> actual = includes();
219         if (actual == null || actual.isEmpty()) {
220             actual = defaultIncludes;
221         }
222         return new PathSelector(directory(), actual, excludes(), useDefaultExcludes).simplify();
223     }
224 
225     /**
226      * {@return in which context the source files will be used}.
227      */
228     @Override
229     public ProjectScope scope() {
230         return scope;
231     }
232 
233     /**
234      * {@return the language of the source files}.
235      */
236     @Override
237     public Language language() {
238         return language;
239     }
240 
241     /**
242      * {@return the name of the Java module (or other language-specific module) which is built by the sources}.
243      */
244     @Override
245     public Optional<String> module() {
246         return Optional.ofNullable(moduleName);
247     }
248 
249     /**
250      * {@return the version of the platform where the code will be executed}.
251      */
252     @Override
253     public Optional<Version> targetVersion() {
254         return Optional.ofNullable(targetVersion);
255     }
256 
257     /**
258      * {@return an explicit target path, overriding the default value}.
259      */
260     @Override
261     public Optional<Path> targetPath() {
262         return Optional.ofNullable(targetPath);
263     }
264 
265     /**
266      * {@return whether resources are filtered to replace tokens with parameterized values}.
267      */
268     @Override
269     public boolean stringFiltering() {
270         return stringFiltering;
271     }
272 
273     /**
274      * {@return whether the directory described by this source element should be included in the build}.
275      */
276     @Override
277     public boolean enabled() {
278         return enabled;
279     }
280 
281     /**
282      * {@return a hash code value computed from all properties}.
283      */
284     @Override
285     public int hashCode() {
286         return Objects.hash(
287                 directory,
288                 includes,
289                 excludes,
290                 scope,
291                 language,
292                 moduleName,
293                 targetVersion,
294                 targetPath,
295                 stringFiltering,
296                 enabled);
297     }
298 
299     /**
300      * {@return whether the two objects are of the same class with equal property values}.
301      *
302      * @param obj the other object to compare with this object, or {@code null}
303      */
304     @Override
305     public boolean equals(Object obj) {
306         if (this == obj) {
307             return true;
308         }
309         if (obj instanceof DefaultSourceRoot other) {
310             return directory.equals(other.directory)
311                     && includes.equals(other.includes)
312                     && excludes.equals(other.excludes)
313                     && Objects.equals(scope, other.scope)
314                     && Objects.equals(language, other.language)
315                     && Objects.equals(moduleName, other.moduleName)
316                     && Objects.equals(targetVersion, other.targetVersion)
317                     && stringFiltering == other.stringFiltering
318                     && enabled == other.enabled;
319         }
320         return false;
321     }
322 }