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.services;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Optional;
26  import java.util.function.Predicate;
27  
28  import org.apache.maven.api.Artifact;
29  import org.apache.maven.api.DependencyCoordinates;
30  import org.apache.maven.api.JavaPathType;
31  import org.apache.maven.api.PathScope;
32  import org.apache.maven.api.PathType;
33  import org.apache.maven.api.Project;
34  import org.apache.maven.api.Session;
35  import org.apache.maven.api.annotations.Experimental;
36  import org.apache.maven.api.annotations.Immutable;
37  import org.apache.maven.api.annotations.Nonnull;
38  import org.apache.maven.api.annotations.NotThreadSafe;
39  import org.apache.maven.api.annotations.Nullable;
40  
41  /**
42   * A request to collect the transitive dependencies and to build a dependency graph from them. There are three ways to
43   * create a dependency graph. First, only the root dependency can be given. Second, a root dependency and direct
44   * dependencies can be specified in which case the specified direct dependencies are merged with the direct dependencies
45   * retrieved from the artifact descriptor of the root dependency. And last, only direct dependencies can be specified in
46   * which case the root node of the resulting graph has no associated dependency.
47   *
48   * @since 4.0.0
49   */
50  @Experimental
51  @Immutable
52  public interface DependencyResolverRequest {
53  
54      enum RequestType {
55          COLLECT,
56          FLATTEN,
57          RESOLVE
58      }
59  
60      @Nonnull
61      Session getSession();
62  
63      @Nonnull
64      RequestType getRequestType();
65  
66      @Nonnull
67      Optional<Project> getProject();
68  
69      @Nonnull
70      Optional<Artifact> getRootArtifact();
71  
72      @Nonnull
73      Optional<DependencyCoordinates> getRoot();
74  
75      @Nonnull
76      Collection<DependencyCoordinates> getDependencies();
77  
78      @Nonnull
79      Collection<DependencyCoordinates> getManagedDependencies();
80  
81      boolean getVerbose();
82  
83      @Nullable
84      PathScope getPathScope();
85  
86      /**
87       * Returns a filter for the types of path (class-path, module-path, …) accepted by the tool.
88       * For example, if a Java tools accepts only class-path elements, then the filter should return
89       * {@code true} for {@link JavaPathType#CLASSES} and {@code false} for {@link JavaPathType#MODULES}.
90       * If no filter is explicitly set, then the default is a filter accepting everything.
91       *
92       * @return a filter for the types of path (class-path, module-path, …) accepted by the tool
93       */
94      @Nullable
95      Predicate<PathType> getPathTypeFilter();
96  
97      @Nonnull
98      static DependencyResolverRequestBuilder builder() {
99          return new DependencyResolverRequestBuilder();
100     }
101 
102     @Nonnull
103     static DependencyResolverRequest build(Session session, RequestType requestType, Artifact rootArtifact) {
104         return new DependencyResolverRequestBuilder()
105                 .session(session)
106                 .requestType(requestType)
107                 .rootArtifact(rootArtifact)
108                 .pathScope(PathScope.MAIN_RUNTIME)
109                 .build();
110     }
111 
112     @Nonnull
113     static DependencyResolverRequest build(Session session, RequestType requestType, Project project) {
114         return build(session, requestType, project, PathScope.MAIN_RUNTIME);
115     }
116 
117     @Nonnull
118     static DependencyResolverRequest build(Session session, RequestType requestType, Project project, PathScope scope) {
119         return new DependencyResolverRequestBuilder()
120                 .session(session)
121                 .requestType(requestType)
122                 .project(project)
123                 .pathScope(scope)
124                 .build();
125     }
126 
127     @Nonnull
128     static DependencyResolverRequest build(Session session, RequestType requestType, DependencyCoordinates dependency) {
129         return build(session, requestType, dependency, PathScope.MAIN_RUNTIME);
130     }
131 
132     @Nonnull
133     static DependencyResolverRequest build(
134             Session session, RequestType requestType, DependencyCoordinates dependency, PathScope scope) {
135         return new DependencyResolverRequestBuilder()
136                 .session(session)
137                 .requestType(requestType)
138                 .dependency(dependency)
139                 .pathScope(scope)
140                 .build();
141     }
142 
143     @Nonnull
144     static DependencyResolverRequest build(
145             Session session, RequestType requestType, List<DependencyCoordinates> dependencies) {
146         return build(session, requestType, dependencies, PathScope.MAIN_RUNTIME);
147     }
148 
149     @Nonnull
150     static DependencyResolverRequest build(
151             Session session, RequestType requestType, List<DependencyCoordinates> dependencies, PathScope scope) {
152         return new DependencyResolverRequestBuilder()
153                 .session(session)
154                 .requestType(requestType)
155                 .dependencies(dependencies)
156                 .pathScope(scope)
157                 .build();
158     }
159 
160     @NotThreadSafe
161     class DependencyResolverRequestBuilder {
162 
163         Session session;
164         RequestType requestType;
165         Project project;
166         Artifact rootArtifact;
167         DependencyCoordinates root;
168         List<DependencyCoordinates> dependencies = Collections.emptyList();
169         List<DependencyCoordinates> managedDependencies = Collections.emptyList();
170         boolean verbose;
171         PathScope pathScope;
172         Predicate<PathType> pathTypeFilter;
173 
174         DependencyResolverRequestBuilder() {}
175 
176         @Nonnull
177         public DependencyResolverRequestBuilder session(@Nonnull Session session) {
178             this.session = session;
179             return this;
180         }
181 
182         @Nonnull
183         public DependencyResolverRequestBuilder requestType(@Nonnull RequestType requestType) {
184             this.requestType = requestType;
185             return this;
186         }
187 
188         @Nonnull
189         public DependencyResolverRequestBuilder project(@Nullable Project project) {
190             this.project = project;
191             return this;
192         }
193 
194         /**
195          * Sets the root artifact for the dependency graph.
196          * This must not be confused with {@link #root(DependencyCoordinates)}: The root <em>dependency</em>, like any
197          * other specified dependency, will be subject to dependency collection/resolution, i.e. should have an artifact
198          * descriptor and a corresponding artifact file. The root <em>artifact</em> on the other hand is only used
199          * as a label for the root node of the graph in case no root dependency was specified. As such, the configured
200          * root artifact is ignored if {@link #root(DependencyCoordinates)} has been set.
201          *
202          * @param rootArtifact the root artifact for the dependency graph, may be {@code null}
203          * @return this request for chaining, never {@code null}
204          */
205         @Nonnull
206         public DependencyResolverRequestBuilder rootArtifact(@Nullable Artifact rootArtifact) {
207             this.rootArtifact = rootArtifact;
208             return this;
209         }
210 
211         /**
212          * @param root The root dependency
213          * @return this request for chaining, never {@code null}
214          */
215         @Nonnull
216         public DependencyResolverRequestBuilder root(@Nonnull DependencyCoordinates root) {
217             this.root = root;
218             return this;
219         }
220 
221         /**
222          * Sets the direct dependencies. If both a root dependency and direct dependencies are given in the request, the
223          * direct dependencies from the request will be merged with the direct dependencies from the root dependency's
224          * artifact descriptor, giving higher priority to the dependencies from the request.
225          *
226          * @param dependencies the direct dependencies, may be {@code null}
227          * @return this request for chaining, never {@code null}
228          */
229         @Nonnull
230         public DependencyResolverRequestBuilder dependencies(@Nullable List<DependencyCoordinates> dependencies) {
231             this.dependencies = (dependencies != null) ? dependencies : Collections.emptyList();
232             return this;
233         }
234 
235         /**
236          * Adds the specified direct dependency.
237          *
238          * @param dependency the dependency to add, may be {@code null}
239          * @return this request for chaining, never {@code null}
240          */
241         @Nonnull
242         public DependencyResolverRequestBuilder dependency(@Nullable DependencyCoordinates dependency) {
243             if (dependency != null) {
244                 if (this.dependencies.isEmpty()) {
245                     this.dependencies = new ArrayList<>();
246                 }
247                 this.dependencies.add(dependency);
248             }
249             return this;
250         }
251 
252         /**
253          * Sets the dependency management to apply to transitive dependencies. To clarify, this management does not
254          * apply to
255          * the direct dependencies of the root node.
256          *
257          * @param managedDependencies the dependency management, may be {@code null}
258          * @return this request for chaining, never {@code null}
259          */
260         @Nonnull
261         public DependencyResolverRequestBuilder managedDependencies(
262                 @Nullable List<DependencyCoordinates> managedDependencies) {
263             this.managedDependencies = (managedDependencies != null) ? managedDependencies : Collections.emptyList();
264             return this;
265         }
266 
267         /**
268          * Adds the specified managed dependency.
269          *
270          * @param managedDependency The managed dependency to add, may be {@code null} in which case the call
271          *                          will have no effect.
272          * @return this request for chaining, never {@code null}
273          */
274         @Nonnull
275         public DependencyResolverRequestBuilder managedDependency(@Nullable DependencyCoordinates managedDependency) {
276             if (managedDependency != null) {
277                 if (this.managedDependencies.isEmpty()) {
278                     this.managedDependencies = new ArrayList<>();
279                 }
280                 this.managedDependencies.add(managedDependency);
281             }
282             return this;
283         }
284 
285         /**
286          * Specifies that the collection should be verbose.
287          *
288          * @param verbose whether the collection should be verbose or not
289          * @return this request for chaining, never {@code null}
290          */
291         @Nonnull
292         public DependencyResolverRequestBuilder verbose(boolean verbose) {
293             this.verbose = verbose;
294             return this;
295         }
296 
297         @Nonnull
298         public DependencyResolverRequestBuilder pathScope(@Nullable PathScope pathScope) {
299             this.pathScope = pathScope;
300             return this;
301         }
302 
303         /**
304          * Filters the types of paths to include in the result.
305          * The result will contain only the paths of types for which the predicate returned {@code true}.
306          * It is recommended to apply a filter for retaining only the types of paths of interest,
307          * because it can resolve ambiguities when a path could be of many types.
308          *
309          * @param pathTypeFilter predicate evaluating whether a path type should be included in the result
310          * @return {@code this} for method call chaining
311          */
312         @Nonnull
313         public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Predicate<PathType> pathTypeFilter) {
314             this.pathTypeFilter = pathTypeFilter;
315             return this;
316         }
317 
318         /**
319          * Specifies the type of paths to include in the result. This is a convenience method for
320          * {@link #pathTypeFilter(Predicate)} using {@link Collection#contains(Object)} as the filter.
321          *
322          * @param desiredTypes the type of paths to include in the result
323          * @return {@code this} for method call chaining
324          */
325         @Nonnull
326         public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Collection<? extends PathType> desiredTypes) {
327             return pathTypeFilter(desiredTypes::contains);
328         }
329 
330         @Nonnull
331         public DependencyResolverRequest build() {
332             return new DefaultDependencyResolverRequest(
333                     session,
334                     requestType,
335                     project,
336                     rootArtifact,
337                     root,
338                     dependencies,
339                     managedDependencies,
340                     verbose,
341                     pathScope,
342                     pathTypeFilter);
343         }
344 
345         static class DefaultDependencyResolverRequest extends BaseRequest implements DependencyResolverRequest {
346             private final RequestType requestType;
347             private final Project project;
348             private final Artifact rootArtifact;
349             private final DependencyCoordinates root;
350             private final Collection<DependencyCoordinates> dependencies;
351             private final Collection<DependencyCoordinates> managedDependencies;
352             private final boolean verbose;
353             private final PathScope pathScope;
354             private final Predicate<PathType> pathTypeFilter;
355 
356             /**
357              * Creates a request with the specified properties.
358              *
359              * @param session      {@link Session}
360              * @param rootArtifact The root dependency whose transitive dependencies should be collected, may be {@code
361              *                     null}.
362              */
363             @SuppressWarnings("checkstyle:ParameterNumber")
364             DefaultDependencyResolverRequest(
365                     @Nonnull Session session,
366                     @Nonnull RequestType requestType,
367                     @Nullable Project project,
368                     @Nullable Artifact rootArtifact,
369                     @Nullable DependencyCoordinates root,
370                     @Nonnull Collection<DependencyCoordinates> dependencies,
371                     @Nonnull Collection<DependencyCoordinates> managedDependencies,
372                     boolean verbose,
373                     @Nullable PathScope pathScope,
374                     @Nullable Predicate<PathType> pathTypeFilter) {
375                 super(session);
376                 this.requestType = nonNull(requestType, "requestType cannot be null");
377                 this.project = project;
378                 this.rootArtifact = rootArtifact;
379                 this.root = root;
380                 this.dependencies = unmodifiable(nonNull(dependencies, "dependencies cannot be null"));
381                 this.managedDependencies =
382                         unmodifiable(nonNull(managedDependencies, "managedDependencies cannot be null"));
383                 this.verbose = verbose;
384                 this.pathScope = pathScope;
385                 this.pathTypeFilter = (pathTypeFilter != null) ? pathTypeFilter : (t) -> true;
386                 if (verbose && requestType != RequestType.COLLECT) {
387                     throw new IllegalArgumentException("verbose cannot only be true when collecting dependencies");
388                 }
389             }
390 
391             @Nonnull
392             @Override
393             public RequestType getRequestType() {
394                 return requestType;
395             }
396 
397             @Nonnull
398             @Override
399             public Optional<Project> getProject() {
400                 return Optional.ofNullable(project);
401             }
402 
403             @Nonnull
404             @Override
405             public Optional<Artifact> getRootArtifact() {
406                 return Optional.ofNullable(rootArtifact);
407             }
408 
409             @Nonnull
410             @Override
411             public Optional<DependencyCoordinates> getRoot() {
412                 return Optional.ofNullable(root);
413             }
414 
415             @Nonnull
416             @Override
417             public Collection<DependencyCoordinates> getDependencies() {
418                 return dependencies;
419             }
420 
421             @Nonnull
422             @Override
423             public Collection<DependencyCoordinates> getManagedDependencies() {
424                 return managedDependencies;
425             }
426 
427             @Override
428             public boolean getVerbose() {
429                 return verbose;
430             }
431 
432             @Override
433             public PathScope getPathScope() {
434                 return pathScope;
435             }
436 
437             @Override
438             public Predicate<PathType> getPathTypeFilter() {
439                 return pathTypeFilter;
440             }
441 
442             @Nonnull
443             @Override
444             public String toString() {
445                 return getRoot() + " -> " + getDependencies();
446             }
447         }
448     }
449 }