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.FileSystem;
22  import java.nio.file.Path;
23  import java.nio.file.PathMatcher;
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<PathMatcher> includes;
43  
44      private final List<PathMatcher> 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          String value = nonBlank(source.getDirectory());
69          if (value == null) {
70              throw new IllegalArgumentException("Source declaration without directory value.");
71          }
72          directory = baseDir.resolve(value);
73          FileSystem fs = directory.getFileSystem();
74          includes = matchers(fs, source.getIncludes());
75          excludes = matchers(fs, source.getExcludes());
76          stringFiltering = source.isStringFiltering();
77          enabled = source.isEnabled();
78          moduleName = nonBlank(source.getModule());
79  
80          value = nonBlank(source.getScope());
81          scope = (value != null) ? session.requireProjectScope(value) : ProjectScope.MAIN;
82  
83          value = nonBlank(source.getLang());
84          language = (value != null) ? session.requireLanguage(value) : Language.JAVA_FAMILY;
85  
86          value = nonBlank(source.getTargetVersion());
87          targetVersion = (value != null) ? session.parseVersion(value) : null;
88  
89          value = nonBlank(source.getTargetPath());
90          targetPath = (value != null) ? baseDir.resolve(value) : null;
91      }
92  
93      /**
94       * Creates a new instance from the given resource.
95       * This is used for migration from the previous way of declaring resources.
96       *
97       * @param baseDir the base directory for resolving relative paths
98       * @param scope the scope of the resource (main or test)
99       * @param resource a resource element from the model
100      */
101     public DefaultSourceRoot(final Path baseDir, ProjectScope scope, Resource resource) {
102         String value = nonBlank(resource.getDirectory());
103         if (value == null) {
104             throw new IllegalArgumentException("Source declaration without directory value.");
105         }
106         directory = baseDir.resolve(value).normalize();
107         FileSystem fs = directory.getFileSystem();
108         includes = matchers(fs, resource.getIncludes());
109         excludes = matchers(fs, resource.getExcludes());
110         stringFiltering = Boolean.parseBoolean(resource.getFiltering());
111         enabled = true;
112         moduleName = null;
113         this.scope = scope;
114         language = Language.RESOURCES;
115         targetVersion = null;
116         targetPath = null;
117     }
118 
119     /**
120      * Creates a new instance for the given directory and scope.
121      *
122      * @param scope scope of source code (main or test)
123      * @param language language of the source code
124      * @param directory directory of the source code
125      */
126     public DefaultSourceRoot(final ProjectScope scope, final Language language, final Path directory) {
127         this.scope = Objects.requireNonNull(scope);
128         this.language = Objects.requireNonNull(language);
129         this.directory = Objects.requireNonNull(directory);
130         includes = List.of();
131         excludes = List.of();
132         moduleName = null;
133         targetVersion = null;
134         targetPath = null;
135         stringFiltering = false;
136         enabled = true;
137     }
138 
139     /**
140      * Creates a new instance for the given directory and scope.
141      *
142      * @param scope scope of source code (main or test)
143      * @param language language of the source code
144      * @param directory directory of the source code
145      */
146     public DefaultSourceRoot(
147             final ProjectScope scope,
148             final Language language,
149             final Path directory,
150             List<PathMatcher> includes,
151             List<PathMatcher> excludes) {
152         this.scope = Objects.requireNonNull(scope);
153         this.language = language;
154         this.directory = Objects.requireNonNull(directory);
155         this.includes = includes != null ? List.copyOf(includes) : List.of();
156         this.excludes = excludes != null ? List.copyOf(excludes) : List.of();
157         moduleName = null;
158         targetVersion = null;
159         targetPath = null;
160         stringFiltering = false;
161         enabled = true;
162     }
163 
164     /**
165      * {@return the given value as a trimmed non-blank string, or null otherwise}.
166      */
167     private static String nonBlank(String value) {
168         if (value != null) {
169             value = value.trim();
170             if (value.isBlank()) {
171                 value = null;
172             }
173         }
174         return value;
175     }
176 
177     /**
178      * Creates a path matcher for each pattern.
179      * The path separator is {@code /} on all platforms, including Windows.
180      * The prefix before the {@code :} character is the syntax.
181      * If no syntax is specified, {@code "glob"} is assumed.
182      *
183      * @param fs the file system of the root directory
184      * @param patterns the patterns for which to create path matcher
185      * @return a path matcher for each pattern
186      */
187     private static List<PathMatcher> matchers(FileSystem fs, List<String> patterns) {
188         final var matchers = new PathMatcher[patterns.size()];
189         for (int i = 0; i < matchers.length; i++) {
190             String rawPattern = patterns.get(i);
191             String pattern = rawPattern.contains(":") ? rawPattern : "glob:" + rawPattern;
192             matchers[i] = new PathMatcher() {
193                 final PathMatcher delegate = fs.getPathMatcher(pattern);
194 
195                 @Override
196                 public boolean matches(Path path) {
197                     return delegate.matches(path);
198                 }
199 
200                 @Override
201                 public String toString() {
202                     return rawPattern;
203                 }
204             };
205         }
206         return List.of(matchers);
207     }
208 
209     /**
210      * {@return the root directory where the sources are stored}.
211      */
212     @Override
213     public Path directory() {
214         return directory;
215     }
216 
217     /**
218      * {@return the list of pattern matchers for the files to include}.
219      */
220     @Override
221     @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
222     public List<PathMatcher> includes() {
223         return includes;
224     }
225 
226     /**
227      * {@return the list of pattern matchers for the files to exclude}.
228      */
229     @Override
230     @SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because unmodifiable
231     public List<PathMatcher> excludes() {
232         return excludes;
233     }
234 
235     /**
236      * {@return in which context the source files will be used}.
237      */
238     @Override
239     public ProjectScope scope() {
240         return scope;
241     }
242 
243     /**
244      * {@return the language of the source files}.
245      */
246     @Override
247     public Language language() {
248         return language;
249     }
250 
251     /**
252      * {@return the name of the Java module (or other language-specific module) which is built by the sources}.
253      */
254     @Override
255     public Optional<String> module() {
256         return Optional.ofNullable(moduleName);
257     }
258 
259     /**
260      * {@return the version of the platform where the code will be executed}.
261      */
262     @Override
263     public Optional<Version> targetVersion() {
264         return Optional.ofNullable(targetVersion);
265     }
266 
267     /**
268      * {@return an explicit target path, overriding the default value}.
269      */
270     @Override
271     public Optional<Path> targetPath() {
272         return Optional.ofNullable(targetPath);
273     }
274 
275     /**
276      * {@return whether resources are filtered to replace tokens with parameterized values}.
277      */
278     @Override
279     public boolean stringFiltering() {
280         return stringFiltering;
281     }
282 
283     /**
284      * {@return whether the directory described by this source element should be included in the build}.
285      */
286     @Override
287     public boolean enabled() {
288         return enabled;
289     }
290 
291     /**
292      * {@return a hash code value computed from all properties}.
293      */
294     @Override
295     public int hashCode() {
296         return Objects.hash(
297                 directory,
298                 includes,
299                 excludes,
300                 scope,
301                 language,
302                 moduleName,
303                 targetVersion,
304                 targetPath,
305                 stringFiltering,
306                 enabled);
307     }
308 
309     /**
310      * {@return whether the two objects are of the same class with equal property values}.
311      *
312      * @param obj the other object to compare with this object, or {@code null}
313      */
314     @Override
315     public boolean equals(Object obj) {
316         if (this == obj) {
317             return true;
318         }
319         if (obj instanceof DefaultSourceRoot other) {
320             return directory.equals(other.directory)
321                     && includes.equals(other.includes)
322                     && excludes.equals(other.excludes)
323                     && Objects.equals(scope, other.scope)
324                     && Objects.equals(language, other.language)
325                     && Objects.equals(moduleName, other.moduleName)
326                     && Objects.equals(targetVersion, other.targetVersion)
327                     && stringFiltering == other.stringFiltering
328                     && enabled == other.enabled;
329         }
330         return false;
331     }
332 }