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.plugin.plugin;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.net.URI;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.util.*;
32  import java.util.stream.Collectors;
33  import java.util.stream.Stream;
34  
35  import org.apache.maven.artifact.Artifact;
36  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
37  import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter;
38  import org.apache.maven.execution.MavenSession;
39  import org.apache.maven.plugin.MojoExecutionException;
40  import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
41  import org.apache.maven.plugin.descriptor.MojoDescriptor;
42  import org.apache.maven.plugin.descriptor.PluginDescriptor;
43  import org.apache.maven.plugins.annotations.LifecyclePhase;
44  import org.apache.maven.plugins.annotations.Mojo;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.apache.maven.plugins.annotations.ResolutionScope;
47  import org.apache.maven.project.MavenProject;
48  import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
49  import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
50  import org.apache.maven.tools.plugin.PluginDescriptorHelper;
51  import org.apache.maven.tools.plugin.PluginToolsRequest;
52  import org.apache.maven.tools.plugin.extractor.ExtractionException;
53  import org.apache.maven.tools.plugin.generator.GeneratorException;
54  import org.apache.maven.tools.plugin.generator.GeneratorUtils;
55  import org.apache.maven.tools.plugin.generator.PluginDescriptorFilesGenerator;
56  import org.apache.maven.tools.plugin.scanner.MojoScanner;
57  import org.codehaus.plexus.component.repository.ComponentDependency;
58  import org.codehaus.plexus.util.ReaderFactory;
59  import org.codehaus.plexus.util.io.CachingOutputStream;
60  import org.codehaus.plexus.util.io.CachingWriter;
61  import org.objectweb.asm.*;
62  import org.sonatype.plexus.build.incremental.BuildContext;
63  
64  import static org.objectweb.asm.Opcodes.*;
65  import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
66  
67  /**
68   * <p>
69   * Generate a plugin descriptor.
70   * </p>
71   * <p>
72   * <b>Note:</b> Since 3.0, for Java plugin annotations support,
73   * default <a href="http://maven.apache.org/ref/current/maven-core/lifecycles.html">phase</a>
74   * defined by this goal is after the "compilation" of any scripts. This doesn't override
75   * <a href="/ref/current/maven-core/default-bindings.html#Bindings_for_maven-plugin_packaging">the default binding coded
76   * at generate-resources phase</a> in Maven core.
77   * </p>
78   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
79   * @since 2.0
80   */
81  @Mojo(
82          name = "descriptor",
83          defaultPhase = LifecyclePhase.PROCESS_CLASSES,
84          requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
85          threadSafe = true)
86  public class DescriptorGeneratorMojo extends AbstractGeneratorMojo {
87      private static final String VALUE_AUTO = "auto";
88  
89      /**
90       * The directory where the generated <code>plugin.xml</code> file will be put.
91       */
92      @Parameter(defaultValue = "${project.build.outputDirectory}/META-INF/maven", readonly = true)
93      private File outputDirectory;
94  
95      /**
96       * The directory where the generated class files will be put.
97       */
98      @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true)
99      private File classesOutputDirectory;
100 
101     /**
102      * The file encoding of the source files.
103      *
104      * @since 2.5
105      */
106     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
107     private String encoding;
108 
109     /**
110      * A flag to disable generation of the <code>plugin.xml</code> in favor of a hand authored plugin descriptor.
111      *
112      * @since 2.6
113      */
114     @Parameter(defaultValue = "false")
115     private boolean skipDescriptor;
116 
117     /**
118      * <p>
119      * The role names of mojo extractors to use.
120      * </p>
121      * <p>
122      * If not set, all mojo extractors will be used. If set to an empty extractor name, no mojo extractors
123      * will be used.
124      * </p>
125      * Example:
126      * <pre>
127      *  &lt;!-- Use all mojo extractors --&gt;
128      *  &lt;extractors/&gt;
129      *
130      *  &lt;!-- Use no mojo extractors --&gt;
131      *  &lt;extractors&gt;
132      *      &lt;extractor/&gt;
133      *  &lt;/extractors&gt;
134      *
135      *  &lt;!-- Use only bsh mojo extractor --&gt;
136      *  &lt;extractors&gt;
137      *      &lt;extractor&gt;bsh&lt;/extractor&gt;
138      *  &lt;/extractors&gt;
139      * </pre>
140      * The extractors with the following names ship with {@code maven-plugin-tools}:
141      * <ol>
142      *  <li>{@code java-annotations}</li>
143      *  <li>{@code java-javadoc}, deprecated</li>
144      *  <li>{@code ant}, deprecated</li>
145      *  <li>{@code bsh}, deprecated</li>
146      * </ol>
147      */
148     @Parameter
149     private Set<String> extractors;
150 
151     /**
152      * By default, an exception is throw if no mojo descriptor is found. As the maven-plugin is defined in core, the
153      * descriptor generator mojo is bound to generate-resources phase.
154      * But for annotations, the compiled classes are needed, so skip error
155      *
156      * @since 3.0
157      */
158     @Parameter(property = "maven.plugin.skipErrorNoDescriptorsFound", defaultValue = "false")
159     private boolean skipErrorNoDescriptorsFound;
160 
161     /**
162      * Flag controlling is "expected dependencies in provided scope" check to be performed or not. Default value:
163      * {@code true}.
164      *
165      * @since 3.6.3
166      */
167     @Parameter(defaultValue = "true", property = "maven.plugin.checkExpectedProvidedScope")
168     private boolean checkExpectedProvidedScope = true;
169 
170     /**
171      * List of {@code groupId} strings of artifact coordinates that are expected to be in "provided" scope. Default
172      * value: {@code ["org.apache.maven"]}.
173      *
174      * @since 3.6.3
175      */
176     @Parameter
177     private List<String> expectedProvidedScopeGroupIds = Collections.singletonList("org.apache.maven");
178 
179     /**
180      * List of {@code groupId:artifactId} strings of artifact coordinates that are to be excluded from "expected
181      * provided scope" check. Default value:
182      * {@code ["org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr", "org.apache.maven:plexus-utils"]}.
183      *
184      * @since 3.6.3
185      */
186     @Parameter
187     private List<String> expectedProvidedScopeExclusions = Arrays.asList(
188             "org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr", "org.apache.maven:plexus-utils");
189 
190     /**
191      * Specify the dependencies as {@code groupId:artifactId} containing (abstract) Mojos, to filter
192      * dependencies scanned at runtime and focus on dependencies that are really useful to Mojo analysis.
193      * By default, the value is {@code null} and all dependencies are scanned (as before this parameter was added).
194      * If specified in the configuration with no children, no dependencies are scanned.
195      *
196      * @since 3.5
197      */
198     @Parameter
199     private List<String> mojoDependencies = null;
200 
201     /**
202      * Creates links to existing external javadoc-generated documentation.
203      * <br>
204      * <b>Notes</b>:
205      * all given links should have a fetchable {@code /package-list} or {@code /element-list} file.
206      * For instance:
207      * <pre>
208      * &lt;externalJavadocBaseUrls&gt;
209      *   &lt;externalJavadocBaseUrl&gt;https://docs.oracle.com/javase/8/docs/api/&lt;/externalJavadocBaseUrl&gt;
210      * &lt;/externalJavadocBaseUrls&gt;
211      * </pre>
212      * is valid because <code>https://docs.oracle.com/javase/8/docs/api/package-list</code> exists.
213      * See <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">
214      * link option of the javadoc tool</a>.
215      * Using this parameter requires connectivity to the given URLs during the goal execution.
216      * @since 3.7.0
217      */
218     @Parameter(property = "externalJavadocBaseUrls", alias = "links")
219     protected List<URI> externalJavadocBaseUrls;
220 
221     /**
222      * The base URL for the Javadoc site containing the current project's API documentation.
223      * This may be relative to the root of the generated Maven site.
224      * It does not need to exist yet at the time when this goal is executed.
225      * Must end with a slash.
226      * <b>In case this is set the javadoc reporting goal should be executed prior to
227      * <a href="../maven-plugin-report-plugin/index.html">Plugin Report</a>.</b>
228      * @since 3.7.0
229      */
230     @Parameter(property = "internalJavadocBaseUrl")
231     protected URI internalJavadocBaseUrl;
232 
233     /**
234      * The version of the javadoc tool (equal to the container JDK version) used to generate the internal javadoc
235      * Only relevant if {@link #internalJavadocBaseUrl} is set.
236      * The default value needs to be overwritten in case toolchains are being used for generating Javadoc.
237      *
238      * @since 3.7.0
239      */
240     @Parameter(property = "internalJavadocVersion", defaultValue = "${java.version}")
241     protected String internalJavadocVersion;
242 
243     /**
244      * The required Java version to set in the plugin descriptor. This is evaluated by Maven 4 and ignored by earlier
245      * Maven versions. Can be either one of the following formats:
246      *
247      * <ul>
248      * <li>A version range which specifies the supported Java versions. It can either use the usual mathematical
249      * syntax like {@code "[1.7,9),[11,)"} or use a single version like {@code "1.8"}. The latter is a short
250      * form for {@code "[1.8,)"}, i.e. denotes the minimum version required.</li>
251      * <li>{@code "auto"} to determine the minimum Java version from the binary class version being generated during
252      * compilation (determined by the extractor).</li>
253      * </ul>
254      *
255      * @since 3.8.0
256      */
257     @Parameter(defaultValue = VALUE_AUTO)
258     String requiredJavaVersion;
259 
260     /**
261      * The required Maven version to set in the plugin descriptor. This is evaluated by Maven 4 and ignored by earlier
262      * Maven versions. Can be either one of the following formats:
263      *
264      * <ul>
265      * <li>A version range which specifies the supported Maven versions. It can either use the usual mathematical
266      * syntax like {@code "[2.0.10,2.1.0),[3.0,)"} or use a single version like {@code "2.2.1"}. The latter is a short
267      * form for {@code "[2.2.1,)"}, i.e. denotes the minimum version required.</li>
268      * <li>{@code "auto"} to determine the minimum Maven version from the POM's Maven prerequisite, or if not set the
269      * referenced Maven Plugin API version.</li>
270      * </ul>
271      * This value takes precedence over the
272      * <a href="https://maven.apache.org/pom.html#Prerequisites">POM's Maven prerequisite</a> in Maven 4.
273      *
274      * @since 3.8.0
275      */
276     @Parameter(defaultValue = VALUE_AUTO)
277     String requiredMavenVersion;
278 
279     private final MavenSession mavenSession;
280 
281     /**
282      * The component used for scanning the source tree for mojos.
283      */
284     private final MojoScanner mojoScanner;
285 
286     protected final BuildContext buildContext;
287 
288     @Inject
289     public DescriptorGeneratorMojo(
290             MavenProject project, MavenSession mavenSession, MojoScanner mojoScanner, BuildContext buildContext) {
291         super(project);
292         this.mavenSession = mavenSession;
293         this.mojoScanner = mojoScanner;
294         this.buildContext = buildContext;
295     }
296 
297     public void generate() throws MojoExecutionException {
298         if (!"maven-plugin".equalsIgnoreCase(project.getArtifactId())
299                 && project.getArtifactId().toLowerCase().startsWith("maven-")
300                 && project.getArtifactId().toLowerCase().endsWith("-plugin")
301                 && !"org.apache.maven.plugins".equals(project.getGroupId())) {
302             getLog().warn(LS + LS + "Artifact Ids of the format maven-___-plugin are reserved for" + LS
303                     + "plugins in the Group Id org.apache.maven.plugins" + LS
304                     + "Please change your artifactId to the format ___-maven-plugin" + LS
305                     + "In the future this error will break the build." + LS + LS);
306         }
307 
308         if (skipDescriptor) {
309             getLog().warn("Execution skipped");
310             return;
311         }
312 
313         if (checkExpectedProvidedScope) {
314             Set<Artifact> wrongScopedArtifacts = dependenciesNotInProvidedScope();
315             if (!wrongScopedArtifacts.isEmpty()) {
316                 StringBuilder message = new StringBuilder(
317                         LS + LS + "Some dependencies of Maven Plugins are expected to be in provided scope." + LS
318                                 + "Please make sure that dependencies listed below declared in POM" + LS
319                                 + "have set '<scope>provided</scope>' as well." + LS + LS
320                                 + "The following dependencies are in wrong scope:" + LS);
321                 for (Artifact artifact : wrongScopedArtifacts) {
322                     message.append(" * ").append(artifact).append(LS);
323                 }
324                 message.append(LS).append(LS);
325 
326                 getLog().warn(message.toString());
327             }
328         }
329 
330         mojoScanner.setActiveExtractors(extractors);
331 
332         // TODO: could use this more, eg in the writing of the plugin descriptor!
333         PluginDescriptor pluginDescriptor = new PluginDescriptor();
334 
335         pluginDescriptor.setGroupId(project.getGroupId());
336 
337         pluginDescriptor.setArtifactId(project.getArtifactId());
338 
339         pluginDescriptor.setVersion(project.getVersion());
340 
341         pluginDescriptor.setGoalPrefix(goalPrefix);
342 
343         pluginDescriptor.setName(project.getName());
344 
345         pluginDescriptor.setDescription(project.getDescription());
346 
347         if (encoding == null || encoding.length() < 1) {
348             getLog().warn("Using platform encoding (" + ReaderFactory.FILE_ENCODING
349                     + " actually) to read mojo source files, i.e. build is platform dependent!");
350         } else {
351             getLog().info("Using '" + encoding + "' encoding to read mojo source files.");
352         }
353 
354         if (internalJavadocBaseUrl != null && !internalJavadocBaseUrl.getPath().endsWith("/")) {
355             throw new MojoExecutionException("Given parameter 'internalJavadocBaseUrl' must end with a slash but is '"
356                     + internalJavadocBaseUrl + "'");
357         }
358         try {
359             List<ComponentDependency> deps = GeneratorUtils.toComponentDependencies(project.getArtifacts());
360             pluginDescriptor.setDependencies(deps);
361 
362             PluginToolsRequest request = new DefaultPluginToolsRequest(project, pluginDescriptor);
363             request.setEncoding(encoding);
364             request.setSkipErrorNoDescriptorsFound(skipErrorNoDescriptorsFound);
365             request.setDependencies(filterMojoDependencies());
366             request.setRepoSession(mavenSession.getRepositorySession());
367             request.setInternalJavadocBaseUrl(internalJavadocBaseUrl);
368             request.setInternalJavadocVersion(internalJavadocVersion);
369             request.setExternalJavadocBaseUrls(externalJavadocBaseUrls);
370             request.setSettings(mavenSession.getSettings());
371 
372             mojoScanner.populatePluginDescriptor(request);
373             request.setPluginDescriptor(extendPluginDescriptor(request));
374 
375             outputDirectory.mkdirs();
376 
377             PluginDescriptorFilesGenerator pluginDescriptorGenerator = new PluginDescriptorFilesGenerator();
378             pluginDescriptorGenerator.execute(outputDirectory, request);
379 
380             // Generate the additional factories for v4 mojos
381             generateFactories(request.getPluginDescriptor());
382 
383             // Generate index for v4 beans
384             generateIndex();
385 
386             buildContext.refresh(outputDirectory);
387         } catch (GeneratorException e) {
388             throw new MojoExecutionException("Error writing plugin descriptor", e);
389         } catch (InvalidPluginDescriptorException | ExtractionException e) {
390             throw new MojoExecutionException(
391                     "Error extracting plugin descriptor: '" + e.getLocalizedMessage() + "'", e);
392         } catch (LinkageError e) {
393             throw new MojoExecutionException(
394                     "The API of the mojo scanner is not compatible with this plugin version."
395                             + " Please check the plugin dependencies configured"
396                             + " in the POM and ensure the versions match.",
397                     e);
398         }
399     }
400 
401     private void generateIndex() throws GeneratorException {
402         try {
403             Set<String> diBeans = new TreeSet<>();
404             try (Stream<Path> paths = Files.walk(classesOutputDirectory.toPath())) {
405                 List<Path> classes = paths.filter(
406                                 p -> p.getFileName().toString().endsWith(".class"))
407                         .collect(Collectors.toList());
408                 for (Path classFile : classes) {
409                     String fileString = classFile.toString();
410                     String className = fileString
411                             .substring(0, fileString.length() - ".class".length())
412                             .replace('/', '.');
413                     try (InputStream is = Files.newInputStream(classFile)) {
414                         ClassReader rdr = new ClassReader(is);
415                         rdr.accept(
416                                 new ClassVisitor(Opcodes.ASM9) {
417                                     String className;
418 
419                                     @Override
420                                     public void visit(
421                                             int version,
422                                             int access,
423                                             String name,
424                                             String signature,
425                                             String superName,
426                                             String[] interfaces) {
427                                         super.visit(version, access, name, signature, superName, interfaces);
428                                         className = name;
429                                     }
430 
431                                     @Override
432                                     public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
433                                         if ("Lorg/apache/maven/api/di/Named;".equals(descriptor)) {
434                                             diBeans.add(className.replace('/', '.'));
435                                         }
436                                         return null;
437                                     }
438                                 },
439                                 ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
440                     }
441 
442                     //                    Class<?> clazz = project.getClassRealm().loadClass(className);
443                     //                    boolean hasQualifier = Stream.of(clazz.getAnnotations())
444                     //                            .flatMap(ann -> Stream.of(ann.getClass().getAnnotations()))
445                     //                            .anyMatch(ann -> "org.apache.maven.api.di.Qualifier"
446                     //                                    .equals(ann.annotationType().getName()));
447                     //                    if (hasQualifier) {
448                     //                        diBeans.add(className);
449                     //                    }
450                 }
451             }
452             Path path = outputDirectory.toPath().resolve("org.apache.maven.api.di.Inject");
453             if (diBeans.isEmpty()) {
454                 Files.deleteIfExists(path);
455             } else {
456                 String nl = System.lineSeparator();
457                 try (CachingWriter w = new CachingWriter(path, StandardCharsets.UTF_8)) {
458                     String content = diBeans.stream().collect(Collectors.joining(nl, "", nl));
459                     w.write(content);
460                 }
461             }
462         } catch (Exception e) {
463             throw new GeneratorException("Unable to generate index for v4 beans", e);
464         }
465     }
466 
467     private void generateFactories(PluginDescriptor pd) throws GeneratorException {
468         try {
469             for (MojoDescriptor md : pd.getMojos()) {
470                 if (md instanceof ExtendedMojoDescriptor && ((ExtendedMojoDescriptor) md).isV4Api()) {
471                     generateFactory(md);
472                 }
473             }
474         } catch (IOException e) {
475             throw new GeneratorException("Unable to generate factories for v4 mojos", e);
476         }
477     }
478 
479     private void generateFactory(MojoDescriptor md) throws IOException {
480         String mojoClassName = md.getImplementation();
481         String packageName = mojoClassName.substring(0, mojoClassName.lastIndexOf('.'));
482         String generatorClassName = mojoClassName.substring(mojoClassName.lastIndexOf('.') + 1) + "Factory";
483         String mojoName = md.getId();
484 
485         getLog().debug("Generating v4 factory for " + mojoClassName);
486 
487         byte[] bin = computeGeneratorClassBytes(packageName, generatorClassName, mojoName, mojoClassName);
488 
489         try (OutputStream os = new CachingOutputStream(classesOutputDirectory
490                 .toPath()
491                 .resolve(packageName.replace('.', '/') + "/" + generatorClassName + ".class"))) {
492             os.write(bin);
493         }
494     }
495 
496     static byte[] computeGeneratorClassBytes(
497             String packageName, String generatorClassName, String mojoName, String mojoClassName) {
498         String mojo = mojoClassName.replace('.', '/');
499         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
500         cw.visitSource(generatorClassName + ".java", null);
501         AnnotationVisitor av = cw.visitAnnotation("Lorg/apache/maven/api/di/Named;", true);
502         av.visit("value", mojoName);
503         av.visitEnd();
504         cw.visitAnnotation("Lorg/apache/maven/api/annotations/Generated;", true).visitEnd();
505         cw.visit(
506                 V1_8,
507                 ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC,
508                 packageName.replace(".", "/") + "/" + generatorClassName,
509                 null,
510                 mojo,
511                 null);
512         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC, "<init>", "()V", null, null);
513         mv.visitCode();
514         mv.visitVarInsn(Opcodes.ALOAD, 0);
515         mv.visitMethodInsn(Opcodes.INVOKESPECIAL, mojo, "<init>", "()V", false);
516         mv.visitInsn(Opcodes.RETURN);
517         mv.visitMaxs(-1, -1);
518         mv.visitEnd();
519         cw.visitEnd();
520         return cw.toByteArray();
521     }
522 
523     private PluginDescriptor extendPluginDescriptor(PluginToolsRequest request) {
524         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
525         pluginDescriptor.setRequiredMavenVersion(getRequiredMavenVersion(request));
526         return PluginDescriptorHelper.setRequiredJavaVersion(pluginDescriptor, getRequiredJavaVersion(request));
527     }
528 
529     private String getRequiredMavenVersion(PluginToolsRequest request) {
530         if (!VALUE_AUTO.equals(requiredMavenVersion)) {
531             return requiredMavenVersion;
532         }
533         getLog().debug("Trying to derive Maven version automatically from project prerequisites...");
534         String requiredMavenVersion =
535                 project.getPrerequisites() != null ? project.getPrerequisites().getMaven() : null;
536         if (requiredMavenVersion == null) {
537             getLog().debug("Trying to derive Maven version automatically from referenced Maven Plugin API artifact "
538                     + "version...");
539             requiredMavenVersion = request.getUsedMavenApiVersion();
540         }
541         if (requiredMavenVersion == null) {
542             getLog().warn("Cannot determine the required Maven version automatically, it is recommended to "
543                     + "configure some explicit value manually.");
544         }
545         return requiredMavenVersion;
546     }
547 
548     private String getRequiredJavaVersion(PluginToolsRequest request) {
549         if (!VALUE_AUTO.equals(requiredJavaVersion)) {
550             return requiredJavaVersion;
551         }
552         String minRequiredJavaVersion = request.getRequiredJavaVersion();
553         if (minRequiredJavaVersion == null) {
554             getLog().warn("Cannot determine the minimally required Java version automatically, it is recommended to "
555                     + "configure some explicit value manually.");
556             return null;
557         }
558 
559         return minRequiredJavaVersion;
560     }
561 
562     /**
563      * Collects all dependencies expected to be in "provided" scope but are NOT in "provided" scope.
564      */
565     private Set<Artifact> dependenciesNotInProvidedScope() {
566         LinkedHashSet<Artifact> wrongScopedDependencies = new LinkedHashSet<>();
567 
568         for (Artifact dependency : project.getArtifacts()) {
569             String ga = dependency.getGroupId() + ":" + dependency.getArtifactId();
570             if (expectedProvidedScopeGroupIds.contains(dependency.getGroupId())
571                     && !expectedProvidedScopeExclusions.contains(ga)
572                     && !Artifact.SCOPE_PROVIDED.equals(dependency.getScope())) {
573                 wrongScopedDependencies.add(dependency);
574             }
575         }
576 
577         return wrongScopedDependencies;
578     }
579 
580     /**
581      * Get dependencies filtered with mojoDependencies configuration.
582      *
583      * @return eventually filtered dependencies, or even <code>null</code> if configured with empty mojoDependencies
584      * list
585      * @see #mojoDependencies
586      */
587     private Set<Artifact> filterMojoDependencies() {
588         Set<Artifact> filteredArtifacts;
589         if (mojoDependencies == null) {
590             filteredArtifacts = new LinkedHashSet<>(project.getArtifacts());
591         } else if (mojoDependencies.isEmpty()) {
592             filteredArtifacts = null;
593         } else {
594             filteredArtifacts = new LinkedHashSet<>();
595 
596             ArtifactFilter filter = new IncludesArtifactFilter(mojoDependencies);
597 
598             for (Artifact artifact : project.getArtifacts()) {
599                 if (filter.include(artifact)) {
600                     filteredArtifacts.add(artifact);
601                 }
602             }
603         }
604 
605         return filteredArtifacts;
606     }
607 }