View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.maven;
6   
7   import com.google.common.base.Predicate;
8   import com.google.common.base.Predicates;
9   import com.google.common.collect.Collections2;
10  import com.jcabi.log.Logger;
11  import com.qulice.spi.ValidationException;
12  import java.util.Collection;
13  import java.util.LinkedList;
14  import org.apache.maven.artifact.Artifact;
15  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
16  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
17  import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException;
18  import org.cactoos.text.Joined;
19  import org.codehaus.plexus.PlexusConstants;
20  import org.codehaus.plexus.PlexusContainer;
21  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
22  import org.codehaus.plexus.context.ContextException;
23  
24  /**
25   * Validator of dependencies.
26   *
27   * @since 0.3
28   * @checkstyle ReturnCountCheck (100 line)
29   */
30  final class DependenciesValidator implements MavenValidator {
31  
32      /**
33       * Separator between lines.
34       */
35      private static final String SEP = "\n\t";
36  
37      @Override
38      @SuppressWarnings("PMD.OnlyOneReturn")
39      public void validate(final MavenEnvironment env)
40          throws ValidationException {
41          if (!env.outdir().exists() || "pom".equals(env.project().getPackaging())) {
42              Logger.info(this, "No dependency analysis in this project");
43              return;
44          }
45          final Collection<String> excludes = env.excludes("dependencies");
46          if (excludes.contains(".*")) {
47              Logger.info(this, "Dependency analysis suppressed in the project via pom.xml");
48              return;
49          }
50          final Collection<String> unused = Collections2.filter(
51              DependenciesValidator.unused(env),
52              Predicates.not(new DependenciesValidator.ExcludePredicate(excludes))
53          );
54          if (!unused.isEmpty()) {
55              Logger.warn(
56                  this,
57                  "Unused declared dependencies found:%s%s",
58                  DependenciesValidator.SEP,
59                  new Joined(DependenciesValidator.SEP, unused).toString()
60              );
61          }
62          final Collection<String> used = Collections2.filter(
63              DependenciesValidator.used(env),
64              Predicates.not(new DependenciesValidator.ExcludePredicate(excludes))
65          );
66          if (!used.isEmpty()) {
67              Logger.warn(
68                  this,
69                  "Used undeclared dependencies found:%s%s",
70                  DependenciesValidator.SEP,
71                  new Joined(DependenciesValidator.SEP, used)
72              );
73          }
74          if (!used.isEmpty() || !unused.isEmpty()) {
75              Logger.info(
76                  this,
77                  "You can suppress this message by <exclude>dependencies:...</exclude> in pom.xml, where <...> is what the dependency name starts with (not a regular expression!)"
78              );
79          }
80          final int failures = used.size() + unused.size();
81          if (failures > 0) {
82              throw new ValidationException(
83                  "%d dependency problem(s) found",
84                  failures
85              );
86          }
87          Logger.info(this, "No dependency problems found");
88      }
89  
90      /**
91       * Analyze the project.
92       * @param env The environment
93       * @return The result of analysis
94       */
95      private static ProjectDependencyAnalysis analyze(
96          final MavenEnvironment env) {
97          try {
98              return ((ProjectDependencyAnalyzer)
99                  ((PlexusContainer)
100                     env.context().get(PlexusConstants.PLEXUS_KEY)
101                 ).lookup(ProjectDependencyAnalyzer.class.getName(), "default")
102             ).analyze(env.project());
103         } catch (final ContextException | ComponentLookupException
104             | ProjectDependencyAnalyzerException ex) {
105             throw new IllegalStateException(ex);
106         }
107     }
108 
109     /**
110      * Find unused artifacts.
111      * @param env Environment
112      * @return Collection of unused artifacts
113      */
114     private static Collection<String> used(final MavenEnvironment env) {
115         final ProjectDependencyAnalysis analysis =
116             DependenciesValidator.analyze(env);
117         final Collection<String> used = new LinkedList<>();
118         for (final Object artifact : analysis.getUsedUndeclaredArtifacts()) {
119             used.add(artifact.toString());
120         }
121         return used;
122     }
123 
124     /**
125      * Find unused artifacts.
126      * @param env Environment
127      * @return Collection of unused artifacts
128      */
129     private static Collection<String> unused(final MavenEnvironment env) {
130         final ProjectDependencyAnalysis analysis =
131             DependenciesValidator.analyze(env);
132         final Collection<String> unused = new LinkedList<>();
133         for (final Object obj : analysis.getUnusedDeclaredArtifacts()) {
134             final Artifact artifact = (Artifact) obj;
135             if (!Artifact.SCOPE_COMPILE.equals(artifact.getScope())) {
136                 continue;
137             }
138             unused.add(artifact.toString());
139         }
140         return unused;
141     }
142 
143     /**
144      * Predicate for excluded dependencies.
145      *
146      * @since 0.1
147      */
148     private static class ExcludePredicate implements Predicate<String> {
149 
150         /**
151          * List of excludes.
152          */
153         private final Collection<String> excludes;
154 
155         /**
156          * Constructor.
157          * @param excludes List of excludes.
158          */
159         ExcludePredicate(final Collection<String> excludes) {
160             this.excludes = excludes;
161         }
162 
163         @Override
164         public boolean apply(final String name) {
165             boolean ignore = false;
166             for (final String exclude : this.excludes) {
167                 if (name.startsWith(exclude)) {
168                     ignore = true;
169                     break;
170                 }
171             }
172             return ignore;
173         }
174     }
175 }