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