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.Objects;
26  import java.util.Optional;
27  import java.util.function.Predicate;
28  
29  import org.apache.maven.api.Artifact;
30  import org.apache.maven.api.DependencyCoordinates;
31  import org.apache.maven.api.JavaPathType;
32  import org.apache.maven.api.PathScope;
33  import org.apache.maven.api.PathType;
34  import org.apache.maven.api.Project;
35  import org.apache.maven.api.RemoteRepository;
36  import org.apache.maven.api.Session;
37  import org.apache.maven.api.annotations.Experimental;
38  import org.apache.maven.api.annotations.Immutable;
39  import org.apache.maven.api.annotations.Nonnull;
40  import org.apache.maven.api.annotations.NotThreadSafe;
41  import org.apache.maven.api.annotations.Nullable;
42  
43  import static java.util.Objects.requireNonNull;
44  
45  /**
46   * A request to collect the transitive dependencies and to build a dependency graph from them. There are three ways to
47   * create a dependency graph. First, only the root dependency can be given. Second, a root dependency and direct
48   * dependencies can be specified in which case the specified direct dependencies are merged with the direct dependencies
49   * retrieved from the artifact descriptor of the root dependency. And last, only direct dependencies can be specified in
50   * which case the root node of the resulting graph has no associated dependency.
51   *
52   * @since 4.0.0
53   */
54  @Experimental
55  @Immutable
56  public interface DependencyResolverRequest extends Request<Session> {
57  
58      enum RequestType {
59          COLLECT,
60          FLATTEN,
61          RESOLVE
62      }
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         RequestTrace trace;
175         RequestType requestType;
176         Project project;
177         Artifact rootArtifact;
178         DependencyCoordinates root;
179         List<DependencyCoordinates> dependencies = Collections.emptyList();
180         List<DependencyCoordinates> managedDependencies = Collections.emptyList();
181         boolean verbose;
182         PathScope pathScope;
183         Predicate<PathType> pathTypeFilter;
184         List<RemoteRepository> repositories;
185 
186         DependencyResolverRequestBuilder() {}
187 
188         @Nonnull
189         public DependencyResolverRequestBuilder session(@Nonnull Session session) {
190             this.session = session;
191             return this;
192         }
193 
194         @Nonnull
195         public DependencyResolverRequestBuilder trace(RequestTrace trace) {
196             this.trace = trace;
197             return this;
198         }
199 
200         @Nonnull
201         public DependencyResolverRequestBuilder requestType(@Nonnull RequestType requestType) {
202             this.requestType = requestType;
203             return this;
204         }
205 
206         @Nonnull
207         public DependencyResolverRequestBuilder project(@Nullable Project project) {
208             this.project = project;
209             return this;
210         }
211 
212         /**
213          * Sets the root artifact for the dependency graph.
214          * This must not be confused with {@link #root(DependencyCoordinates)}: The root <em>dependency</em>, like any
215          * other specified dependency, will be subject to dependency collection/resolution, i.e. should have an artifact
216          * descriptor and a corresponding artifact file. The root <em>artifact</em> on the other hand is only used
217          * as a label for the root node of the graph in case no root dependency was specified. As such, the configured
218          * root artifact is ignored if {@link #root(DependencyCoordinates)} has been set.
219          *
220          * @param rootArtifact the root artifact for the dependency graph, may be {@code null}
221          * @return this request for chaining, never {@code null}
222          */
223         @Nonnull
224         public DependencyResolverRequestBuilder rootArtifact(@Nullable Artifact rootArtifact) {
225             this.rootArtifact = rootArtifact;
226             return this;
227         }
228 
229         /**
230          * @param root The root dependency
231          * @return this request for chaining, never {@code null}
232          */
233         @Nonnull
234         public DependencyResolverRequestBuilder root(@Nonnull DependencyCoordinates root) {
235             this.root = root;
236             return this;
237         }
238 
239         /**
240          * Sets the direct dependencies. If both a root dependency and direct dependencies are given in the request, the
241          * direct dependencies from the request will be merged with the direct dependencies from the root dependency's
242          * artifact descriptor, giving higher priority to the dependencies from the request.
243          *
244          * @param dependencies the direct dependencies, may be {@code null}
245          * @return this request for chaining, never {@code null}
246          */
247         @Nonnull
248         public DependencyResolverRequestBuilder dependencies(@Nullable List<DependencyCoordinates> dependencies) {
249             this.dependencies = (dependencies != null) ? dependencies : Collections.emptyList();
250             return this;
251         }
252 
253         /**
254          * Adds the specified direct dependency.
255          *
256          * @param dependency the dependency to add, may be {@code null}
257          * @return this request for chaining, never {@code null}
258          */
259         @Nonnull
260         public DependencyResolverRequestBuilder dependency(@Nullable DependencyCoordinates dependency) {
261             if (dependency != null) {
262                 if (this.dependencies.isEmpty()) {
263                     this.dependencies = new ArrayList<>();
264                 }
265                 this.dependencies.add(dependency);
266             }
267             return this;
268         }
269 
270         /**
271          * Sets the dependency management to apply to transitive dependencies. To clarify, this management does not
272          * apply to
273          * the direct dependencies of the root node.
274          *
275          * @param managedDependencies the dependency management, may be {@code null}
276          * @return this request for chaining, never {@code null}
277          */
278         @Nonnull
279         public DependencyResolverRequestBuilder managedDependencies(
280                 @Nullable List<DependencyCoordinates> managedDependencies) {
281             this.managedDependencies = (managedDependencies != null) ? managedDependencies : Collections.emptyList();
282             return this;
283         }
284 
285         /**
286          * Adds the specified managed dependency.
287          *
288          * @param managedDependency The managed dependency to add, may be {@code null} in which case the call
289          *                          will have no effect.
290          * @return this request for chaining, never {@code null}
291          */
292         @Nonnull
293         public DependencyResolverRequestBuilder managedDependency(@Nullable DependencyCoordinates managedDependency) {
294             if (managedDependency != null) {
295                 if (this.managedDependencies.isEmpty()) {
296                     this.managedDependencies = new ArrayList<>();
297                 }
298                 this.managedDependencies.add(managedDependency);
299             }
300             return this;
301         }
302 
303         /**
304          * Specifies that the collection should be verbose.
305          *
306          * @param verbose whether the collection should be verbose or not
307          * @return this request for chaining, never {@code null}
308          */
309         @Nonnull
310         public DependencyResolverRequestBuilder verbose(boolean verbose) {
311             this.verbose = verbose;
312             return this;
313         }
314 
315         @Nonnull
316         public DependencyResolverRequestBuilder pathScope(@Nullable PathScope pathScope) {
317             this.pathScope = pathScope;
318             return this;
319         }
320 
321         /**
322          * Filters the types of paths to include in the result.
323          * The result will contain only the paths of types for which the predicate returned {@code true}.
324          * It is recommended to apply a filter for retaining only the types of paths of interest,
325          * because it can resolve ambiguities when a path could be of many types.
326          *
327          * @param pathTypeFilter predicate evaluating whether a path type should be included in the result
328          * @return {@code this} for method call chaining
329          */
330         @Nonnull
331         public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Predicate<PathType> pathTypeFilter) {
332             this.pathTypeFilter = pathTypeFilter;
333             return this;
334         }
335 
336         /**
337          * Specifies the type of paths to include in the result. This is a convenience method for
338          * {@link #pathTypeFilter(Predicate)} using {@link Collection#contains(Object)} as the filter.
339          *
340          * @param desiredTypes the type of paths to include in the result
341          * @return {@code this} for method call chaining
342          */
343         @Nonnull
344         public DependencyResolverRequestBuilder pathTypeFilter(@Nonnull Collection<? extends PathType> desiredTypes) {
345             return pathTypeFilter(desiredTypes::contains);
346         }
347 
348         @Nonnull
349         public DependencyResolverRequestBuilder repositories(@Nonnull List<RemoteRepository> repositories) {
350             this.repositories = repositories;
351             return this;
352         }
353 
354         @Nonnull
355         public DependencyResolverRequest build() {
356             return new DefaultDependencyResolverRequest(
357                     session,
358                     trace,
359                     requestType,
360                     project,
361                     rootArtifact,
362                     root,
363                     dependencies,
364                     managedDependencies,
365                     verbose,
366                     pathScope,
367                     pathTypeFilter,
368                     repositories);
369         }
370 
371         static class DefaultDependencyResolverRequest extends BaseRequest<Session>
372                 implements DependencyResolverRequest {
373 
374             static final class AlwaysTrueFilter implements Predicate<PathType> {
375                 @Override
376                 public boolean test(PathType pathType) {
377                     return true;
378                 }
379 
380                 @Override
381                 public boolean equals(Object obj) {
382                     return obj instanceof AlwaysTrueFilter;
383                 }
384 
385                 @Override
386                 public int hashCode() {
387                     return AlwaysTrueFilter.class.hashCode();
388                 }
389 
390                 @Override
391                 public String toString() {
392                     return "AlwaysTrueFilter[]";
393                 }
394             }
395 
396             private static final Predicate<PathType> DEFAULT_FILTER = new AlwaysTrueFilter();
397 
398             private final RequestType requestType;
399             private final Project project;
400             private final Artifact rootArtifact;
401             private final DependencyCoordinates root;
402             private final Collection<DependencyCoordinates> dependencies;
403             private final Collection<DependencyCoordinates> managedDependencies;
404             private final boolean verbose;
405             private final PathScope pathScope;
406             private final Predicate<PathType> pathTypeFilter;
407             private final List<RemoteRepository> repositories;
408 
409             /**
410              * Creates a request with the specified properties.
411              *
412              * @param session      {@link Session}
413              * @param rootArtifact The root dependency whose transitive dependencies should be collected, may be {@code
414              *                     null}.
415              */
416             @SuppressWarnings("checkstyle:ParameterNumber")
417             DefaultDependencyResolverRequest(
418                     @Nonnull Session session,
419                     @Nullable RequestTrace trace,
420                     @Nonnull RequestType requestType,
421                     @Nullable Project project,
422                     @Nullable Artifact rootArtifact,
423                     @Nullable DependencyCoordinates root,
424                     @Nonnull Collection<DependencyCoordinates> dependencies,
425                     @Nonnull Collection<DependencyCoordinates> managedDependencies,
426                     boolean verbose,
427                     @Nullable PathScope pathScope,
428                     @Nullable Predicate<PathType> pathTypeFilter,
429                     @Nullable List<RemoteRepository> repositories) {
430                 super(session, trace);
431                 this.requestType = requireNonNull(requestType, "requestType cannot be null");
432                 this.project = project;
433                 this.rootArtifact = rootArtifact;
434                 this.root = root;
435                 this.dependencies = List.copyOf(requireNonNull(dependencies, "dependencies cannot be null"));
436                 this.managedDependencies =
437                         List.copyOf(requireNonNull(managedDependencies, "managedDependencies cannot be null"));
438                 this.verbose = verbose;
439                 this.pathScope = requireNonNull(pathScope, "pathScope cannot be null");
440                 this.pathTypeFilter = (pathTypeFilter != null) ? pathTypeFilter : DEFAULT_FILTER;
441                 this.repositories = repositories;
442                 if (verbose && requestType != RequestType.COLLECT) {
443                     throw new IllegalArgumentException("verbose cannot only be true when collecting dependencies");
444                 }
445             }
446 
447             @Nonnull
448             @Override
449             public RequestType getRequestType() {
450                 return requestType;
451             }
452 
453             @Nonnull
454             @Override
455             public Optional<Project> getProject() {
456                 return Optional.ofNullable(project);
457             }
458 
459             @Nonnull
460             @Override
461             public Optional<Artifact> getRootArtifact() {
462                 return Optional.ofNullable(rootArtifact);
463             }
464 
465             @Nonnull
466             @Override
467             public Optional<DependencyCoordinates> getRoot() {
468                 return Optional.ofNullable(root);
469             }
470 
471             @Nonnull
472             @Override
473             public Collection<DependencyCoordinates> getDependencies() {
474                 return dependencies;
475             }
476 
477             @Nonnull
478             @Override
479             public Collection<DependencyCoordinates> getManagedDependencies() {
480                 return managedDependencies;
481             }
482 
483             @Override
484             public boolean getVerbose() {
485                 return verbose;
486             }
487 
488             @Override
489             public PathScope getPathScope() {
490                 return pathScope;
491             }
492 
493             @Override
494             public Predicate<PathType> getPathTypeFilter() {
495                 return pathTypeFilter;
496             }
497 
498             @Override
499             public List<RemoteRepository> getRepositories() {
500                 return repositories;
501             }
502 
503             @Override
504             public boolean equals(Object o) {
505                 return o instanceof DefaultDependencyResolverRequest that
506                         && verbose == that.verbose
507                         && requestType == that.requestType
508                         && Objects.equals(project, that.project)
509                         && Objects.equals(rootArtifact, that.rootArtifact)
510                         && Objects.equals(root, that.root)
511                         && Objects.equals(dependencies, that.dependencies)
512                         && Objects.equals(managedDependencies, that.managedDependencies)
513                         && Objects.equals(pathScope, that.pathScope)
514                         && Objects.equals(pathTypeFilter, that.pathTypeFilter)
515                         && Objects.equals(repositories, that.repositories);
516             }
517 
518             @Override
519             public int hashCode() {
520                 return Objects.hash(
521                         requestType,
522                         project,
523                         rootArtifact,
524                         root,
525                         dependencies,
526                         managedDependencies,
527                         verbose,
528                         pathScope,
529                         pathTypeFilter,
530                         repositories);
531             }
532 
533             @Override
534             public String toString() {
535                 return "DependencyResolverRequest[" + "requestType="
536                         + requestType + ", project="
537                         + project + ", rootArtifact="
538                         + rootArtifact + ", root="
539                         + root + ", dependencies="
540                         + dependencies + ", managedDependencies="
541                         + managedDependencies + ", verbose="
542                         + verbose + ", pathScope="
543                         + pathScope + ", pathTypeFilter="
544                         + pathTypeFilter + ", repositories="
545                         + repositories + ']';
546             }
547         }
548     }
549 }