1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.tools.plugin.extractor.annotations;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.File;
26 import java.net.MalformedURLException;
27 import java.net.URL;
28 import java.net.URLClassLoader;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.Optional;
40 import java.util.Set;
41 import java.util.TreeMap;
42 import java.util.TreeSet;
43 import java.util.stream.Collectors;
44
45 import com.thoughtworks.qdox.JavaProjectBuilder;
46 import com.thoughtworks.qdox.library.SortedClassLibraryBuilder;
47 import com.thoughtworks.qdox.model.DocletTag;
48 import com.thoughtworks.qdox.model.JavaAnnotatedElement;
49 import com.thoughtworks.qdox.model.JavaClass;
50 import com.thoughtworks.qdox.model.JavaField;
51 import com.thoughtworks.qdox.model.JavaMember;
52 import com.thoughtworks.qdox.model.JavaMethod;
53 import org.apache.maven.artifact.Artifact;
54 import org.apache.maven.artifact.versioning.ComparableVersion;
55 import org.apache.maven.plugin.descriptor.InvalidParameterException;
56 import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
57 import org.apache.maven.plugin.descriptor.MojoDescriptor;
58 import org.apache.maven.plugin.descriptor.PluginDescriptor;
59 import org.apache.maven.plugin.descriptor.Requirement;
60 import org.apache.maven.project.MavenProject;
61 import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
62 import org.apache.maven.tools.plugin.PluginToolsRequest;
63 import org.apache.maven.tools.plugin.extractor.ExtractionException;
64 import org.apache.maven.tools.plugin.extractor.GroupKey;
65 import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
66 import org.apache.maven.tools.plugin.extractor.annotations.converter.ConverterContext;
67 import org.apache.maven.tools.plugin.extractor.annotations.converter.JavaClassConverterContext;
68 import org.apache.maven.tools.plugin.extractor.annotations.converter.JavadocBlockTagsToXhtmlConverter;
69 import org.apache.maven.tools.plugin.extractor.annotations.converter.JavadocInlineTagsToXhtmlConverter;
70 import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent;
71 import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent;
72 import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent;
73 import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent;
74 import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass;
75 import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScanner;
76 import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScannerRequest;
77 import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
78 import org.apache.maven.tools.plugin.util.PluginUtils;
79 import org.codehaus.plexus.archiver.ArchiverException;
80 import org.codehaus.plexus.archiver.UnArchiver;
81 import org.codehaus.plexus.archiver.manager.ArchiverManager;
82 import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
83 import org.codehaus.plexus.logging.AbstractLogEnabled;
84 import org.codehaus.plexus.util.StringUtils;
85 import org.eclipse.aether.RepositorySystem;
86 import org.eclipse.aether.artifact.DefaultArtifact;
87 import org.eclipse.aether.resolution.ArtifactRequest;
88 import org.eclipse.aether.resolution.ArtifactResolutionException;
89 import org.eclipse.aether.resolution.ArtifactResult;
90 import org.objectweb.asm.Opcodes;
91
92
93
94
95
96
97
98
99 @Named(JavaAnnotationsMojoDescriptorExtractor.NAME)
100 @Singleton
101 public class JavaAnnotationsMojoDescriptorExtractor extends AbstractLogEnabled implements MojoDescriptorExtractor {
102 public static final String NAME = "java-annotations";
103
104 private static final GroupKey GROUP_KEY = new GroupKey(GroupKey.JAVA_GROUP, 100);
105
106
107
108
109
110 private static final Map<Integer, String> CLASS_VERSION_TO_JAVA_STRING;
111
112 static {
113 CLASS_VERSION_TO_JAVA_STRING = new HashMap<>();
114 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_1, "1.1");
115 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_2, "1.2");
116 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_3, "1.3");
117 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_4, "1.4");
118 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_5, "1.5");
119 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_6, "1.6");
120 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_7, "1.7");
121 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V1_8, "1.8");
122 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V9, "9");
123 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V10, "10");
124 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V11, "11");
125 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V12, "12");
126 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V13, "13");
127 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V14, "14");
128 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V15, "15");
129 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V16, "16");
130 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V17, "17");
131 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V18, "18");
132 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V19, "19");
133 CLASS_VERSION_TO_JAVA_STRING.put(Opcodes.V20, "20");
134 }
135
136 @Inject
137 MojoAnnotationsScanner mojoAnnotationsScanner;
138
139 @Inject
140 private RepositorySystem repositorySystem;
141
142 @Inject
143 private ArchiverManager archiverManager;
144
145 @Inject
146 private JavadocInlineTagsToXhtmlConverter javadocInlineTagsToHtmlConverter;
147
148 @Inject
149 private JavadocBlockTagsToXhtmlConverter javadocBlockTagsToHtmlConverter;
150
151 @Override
152 public String getName() {
153 return NAME;
154 }
155
156 @Override
157 public boolean isDeprecated() {
158 return false;
159 }
160
161 @Override
162 public GroupKey getGroupKey() {
163 return GROUP_KEY;
164 }
165
166
167
168
169
170
171 @SuppressWarnings("checkstyle:magicnumber")
172 static final class ClassVersionComparator implements Comparator<Integer> {
173 @Override
174 public int compare(Integer classVersion1, Integer classVersion2) {
175
176 int result = Integer.compare(classVersion1 & 0x00FF, classVersion2 & 0x00FF);
177 if (result == 0) {
178
179 result = Integer.compare(classVersion1, classVersion2);
180 }
181 return result;
182 }
183 }
184
185 @Override
186 public List<MojoDescriptor> execute(PluginToolsRequest request)
187 throws ExtractionException, InvalidPluginDescriptorException {
188 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = scanAnnotations(request);
189
190 Optional<Integer> maxClassVersion = mojoAnnotatedClasses.values().stream()
191 .map(MojoAnnotatedClass::getClassVersion)
192 .max(new ClassVersionComparator());
193 if (maxClassVersion.isPresent()) {
194 String requiredJavaVersion = CLASS_VERSION_TO_JAVA_STRING.get(maxClassVersion.get());
195 if (StringUtils.isBlank(request.getRequiredJavaVersion())
196 || new ComparableVersion(request.getRequiredJavaVersion())
197 .compareTo(new ComparableVersion(requiredJavaVersion))
198 < 0) {
199 request.setRequiredJavaVersion(requiredJavaVersion);
200 }
201 }
202 JavaProjectBuilder builder = scanJavadoc(request, mojoAnnotatedClasses.values());
203 Map<String, JavaClass> javaClassesMap = discoverClasses(builder);
204
205 final JavadocLinkGenerator linkGenerator;
206 if (request.getInternalJavadocBaseUrl() != null
207 || (request.getExternalJavadocBaseUrls() != null
208 && !request.getExternalJavadocBaseUrls().isEmpty())) {
209 linkGenerator = new JavadocLinkGenerator(
210 request.getInternalJavadocBaseUrl(),
211 request.getInternalJavadocVersion(),
212 request.getExternalJavadocBaseUrls(),
213 request.getSettings());
214 } else {
215 linkGenerator = null;
216 }
217
218 populateDataFromJavadoc(builder, mojoAnnotatedClasses, javaClassesMap, linkGenerator);
219
220 return toMojoDescriptors(mojoAnnotatedClasses, request.getPluginDescriptor());
221 }
222
223 private Map<String, MojoAnnotatedClass> scanAnnotations(PluginToolsRequest request) throws ExtractionException {
224 MojoAnnotationsScannerRequest mojoAnnotationsScannerRequest = new MojoAnnotationsScannerRequest();
225
226 File output = new File(request.getProject().getBuild().getOutputDirectory());
227 mojoAnnotationsScannerRequest.setClassesDirectories(Arrays.asList(output));
228
229 mojoAnnotationsScannerRequest.setDependencies(request.getDependencies());
230
231 mojoAnnotationsScannerRequest.setProject(request.getProject());
232
233 Map<String, MojoAnnotatedClass> result = mojoAnnotationsScanner.scan(mojoAnnotationsScannerRequest);
234 request.setUsedMavenApiVersion(mojoAnnotationsScannerRequest.getMavenApiVersion());
235 return result;
236 }
237
238 private JavaProjectBuilder scanJavadoc(
239 PluginToolsRequest request, Collection<MojoAnnotatedClass> mojoAnnotatedClasses)
240 throws ExtractionException {
241
242
243 List<MavenProject> mavenProjects = new ArrayList<>();
244
245
246 Set<Artifact> externalArtifacts = new HashSet<>();
247
248 JavaProjectBuilder builder = new JavaProjectBuilder(new SortedClassLibraryBuilder());
249 builder.setEncoding(request.getEncoding());
250 extendJavaProjectBuilder(builder, request.getProject());
251
252 for (MojoAnnotatedClass mojoAnnotatedClass : mojoAnnotatedClasses) {
253 if (Objects.equals(
254 mojoAnnotatedClass.getArtifact().getArtifactId(),
255 request.getProject().getArtifact().getArtifactId())) {
256 continue;
257 }
258
259 if (!isMojoAnnnotatedClassCandidate(mojoAnnotatedClass)) {
260
261 continue;
262 }
263
264 MavenProject mavenProject =
265 getFromProjectReferences(mojoAnnotatedClass.getArtifact(), request.getProject());
266
267 if (mavenProject != null) {
268 mavenProjects.add(mavenProject);
269 } else {
270 externalArtifacts.add(mojoAnnotatedClass.getArtifact());
271 }
272 }
273
274
275 for (Artifact artifact : externalArtifacts) {
276
277 if (StringUtils.equalsIgnoreCase("tests", artifact.getClassifier())) {
278 extendJavaProjectBuilderWithSourcesJar(builder, artifact, request, "test-sources");
279 } else {
280 extendJavaProjectBuilderWithSourcesJar(builder, artifact, request, "sources");
281 }
282 }
283
284 for (MavenProject mavenProject : mavenProjects) {
285 extendJavaProjectBuilder(builder, mavenProject);
286 }
287
288 return builder;
289 }
290
291 private boolean isMojoAnnnotatedClassCandidate(MojoAnnotatedClass mojoAnnotatedClass) {
292 return mojoAnnotatedClass != null && mojoAnnotatedClass.hasAnnotations();
293 }
294
295
296
297
298 protected void populateDataFromJavadoc(
299 JavaProjectBuilder javaProjectBuilder,
300 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses,
301 Map<String, JavaClass> javaClassesMap,
302 JavadocLinkGenerator linkGenerator) {
303
304 for (Map.Entry<String, MojoAnnotatedClass> entry : mojoAnnotatedClasses.entrySet()) {
305 JavaClass javaClass = javaClassesMap.get(entry.getKey());
306 if (javaClass == null) {
307 continue;
308 }
309
310 MojoAnnotationContent mojoAnnotationContent = entry.getValue().getMojo();
311 if (mojoAnnotationContent != null) {
312 JavaClassConverterContext context = new JavaClassConverterContext(
313 javaClass, javaProjectBuilder, mojoAnnotatedClasses, linkGenerator, javaClass.getLineNumber());
314 mojoAnnotationContent.setDescription(getDescriptionFromElement(javaClass, context));
315
316 DocletTag since = findInClassHierarchy(javaClass, "since");
317 if (since != null) {
318 mojoAnnotationContent.setSince(getRawValueFromTaglet(since, context));
319 }
320
321 DocletTag deprecated = findInClassHierarchy(javaClass, "deprecated");
322 if (deprecated != null) {
323 mojoAnnotationContent.setDeprecated(getRawValueFromTaglet(deprecated, context));
324 }
325 }
326
327 Map<String, JavaAnnotatedElement> fieldsMap = extractFieldsAnnotations(javaClass, javaClassesMap);
328 Map<String, JavaAnnotatedElement> methodsMap = extractMethodsAnnotations(javaClass, javaClassesMap);
329
330
331 Map<String, ParameterAnnotationContent> parameters =
332 getParametersParentHierarchy(entry.getValue(), mojoAnnotatedClasses);
333 parameters = new TreeMap<>(parameters);
334 for (Map.Entry<String, ParameterAnnotationContent> parameter : parameters.entrySet()) {
335 JavaAnnotatedElement element;
336 if (parameter.getValue().isAnnotationOnMethod()) {
337 element = methodsMap.get(parameter.getKey());
338 } else {
339 element = fieldsMap.get(parameter.getKey());
340 }
341
342 if (element == null) {
343 continue;
344 }
345
346 JavaClassConverterContext context = new JavaClassConverterContext(
347 javaClass, ((JavaMember) element).getDeclaringClass(),
348 javaProjectBuilder, mojoAnnotatedClasses,
349 linkGenerator, element.getLineNumber());
350 ParameterAnnotationContent parameterAnnotationContent = parameter.getValue();
351 parameterAnnotationContent.setDescription(getDescriptionFromElement(element, context));
352
353 DocletTag deprecated = element.getTagByName("deprecated");
354 if (deprecated != null) {
355 parameterAnnotationContent.setDeprecated(getRawValueFromTaglet(deprecated, context));
356 }
357
358 DocletTag since = element.getTagByName("since");
359 if (since != null) {
360 parameterAnnotationContent.setSince(getRawValueFromTaglet(since, context));
361 }
362 }
363
364
365 Map<String, ComponentAnnotationContent> components =
366 entry.getValue().getComponents();
367 for (Map.Entry<String, ComponentAnnotationContent> component : components.entrySet()) {
368 JavaAnnotatedElement element = fieldsMap.get(component.getKey());
369 if (element == null) {
370 continue;
371 }
372
373 JavaClassConverterContext context = new JavaClassConverterContext(
374 javaClass, ((JavaMember) element).getDeclaringClass(),
375 javaProjectBuilder, mojoAnnotatedClasses,
376 linkGenerator, javaClass.getLineNumber());
377 ComponentAnnotationContent componentAnnotationContent = component.getValue();
378 componentAnnotationContent.setDescription(getDescriptionFromElement(element, context));
379
380 DocletTag deprecated = element.getTagByName("deprecated");
381 if (deprecated != null) {
382 componentAnnotationContent.setDeprecated(getRawValueFromTaglet(deprecated, context));
383 }
384
385 DocletTag since = element.getTagByName("since");
386 if (since != null) {
387 componentAnnotationContent.setSince(getRawValueFromTaglet(since, context));
388 }
389 }
390 }
391 }
392
393
394
395
396
397
398
399
400 String getDescriptionFromElement(JavaAnnotatedElement element, JavaClassConverterContext context) {
401
402 String comment = element.getComment();
403 if (comment == null) {
404 return null;
405 }
406 StringBuilder description = new StringBuilder(javadocInlineTagsToHtmlConverter.convert(comment, context));
407 for (DocletTag docletTag : element.getTags()) {
408
409 if ("see".equals(docletTag.getName())) {
410 description.append(javadocBlockTagsToHtmlConverter.convert(docletTag, context));
411 }
412 }
413 return description.toString();
414 }
415
416 String getRawValueFromTaglet(DocletTag docletTag, ConverterContext context) {
417
418 return javadocInlineTagsToHtmlConverter.convert(docletTag.getValue(), context);
419 }
420
421
422
423
424
425
426 private DocletTag findInClassHierarchy(JavaClass javaClass, String tagName) {
427 try {
428 DocletTag tag = javaClass.getTagByName(tagName);
429
430 if (tag == null) {
431 JavaClass superClass = javaClass.getSuperJavaClass();
432
433 if (superClass != null) {
434 tag = findInClassHierarchy(superClass, tagName);
435 }
436 }
437
438 return tag;
439 } catch (NoClassDefFoundError e) {
440 if (e.getMessage().replace('/', '.').contains(MojoAnnotationsScanner.V4_API_PLUGIN_PACKAGE)) {
441 return null;
442 }
443 String str;
444 try {
445 str = javaClass.getFullyQualifiedName();
446 } catch (Throwable t) {
447 str = javaClass.getValue();
448 }
449 getLogger().warn("Failed extracting tag '" + tagName + "' from class " + str);
450 throw (NoClassDefFoundError) new NoClassDefFoundError(e.getMessage()).initCause(e);
451 }
452 }
453
454
455
456
457
458
459
460 private Map<String, JavaAnnotatedElement> extractFieldsAnnotations(
461 JavaClass javaClass, Map<String, JavaClass> javaClassesMap) {
462 try {
463 Map<String, JavaAnnotatedElement> rawParams = new TreeMap<>();
464
465
466
467 JavaClass superClass = javaClass.getSuperJavaClass();
468
469 if (superClass != null) {
470 if (!superClass.getFields().isEmpty()) {
471 rawParams = extractFieldsAnnotations(superClass, javaClassesMap);
472 }
473
474 superClass = javaClassesMap.get(superClass.getFullyQualifiedName());
475 if (superClass != null && !superClass.getFields().isEmpty()) {
476 rawParams = extractFieldsAnnotations(superClass, javaClassesMap);
477 }
478 } else {
479
480 rawParams = new TreeMap<>();
481 }
482
483 for (JavaField field : javaClass.getFields()) {
484 rawParams.put(field.getName(), field);
485 }
486
487 return rawParams;
488 } catch (NoClassDefFoundError e) {
489 getLogger().warn("Failed extracting parameters from " + javaClass);
490 throw e;
491 }
492 }
493
494
495
496
497
498
499
500 private Map<String, JavaAnnotatedElement> extractMethodsAnnotations(
501 JavaClass javaClass, Map<String, JavaClass> javaClassesMap) {
502 try {
503 Map<String, JavaAnnotatedElement> rawParams = new TreeMap<>();
504
505
506
507 JavaClass superClass = javaClass.getSuperJavaClass();
508
509 if (superClass != null) {
510 if (!superClass.getMethods().isEmpty()) {
511 rawParams = extractMethodsAnnotations(superClass, javaClassesMap);
512 }
513
514 superClass = javaClassesMap.get(superClass.getFullyQualifiedName());
515 if (superClass != null && !superClass.getMethods().isEmpty()) {
516 rawParams = extractMethodsAnnotations(superClass, javaClassesMap);
517 }
518 } else {
519
520 rawParams = new TreeMap<>();
521 }
522
523 for (JavaMethod method : javaClass.getMethods()) {
524 if (isPublicSetterMethod(method)) {
525 rawParams.put(
526 StringUtils.lowercaseFirstLetter(method.getName().substring(3)), method);
527 }
528 }
529
530 return rawParams;
531 } catch (NoClassDefFoundError e) {
532 if (e.getMessage().replace('/', '.').contains(MojoAnnotationsScanner.V4_API_PLUGIN_PACKAGE)) {
533 return new TreeMap<>();
534 }
535 String str;
536 try {
537 str = javaClass.getFullyQualifiedName();
538 } catch (Throwable t) {
539 str = javaClass.getValue();
540 }
541 getLogger().warn("Failed extracting methods from " + str);
542 throw (NoClassDefFoundError) new NoClassDefFoundError(e.getMessage()).initCause(e);
543 }
544 }
545
546 private boolean isPublicSetterMethod(JavaMethod method) {
547 return method.isPublic()
548 && !method.isStatic()
549 && method.getName().length() > 3
550 && (method.getName().startsWith("add") || method.getName().startsWith("set"))
551 && "void".equals(method.getReturnType().getValue())
552 && method.getParameters().size() == 1;
553 }
554
555 protected Map<String, JavaClass> discoverClasses(JavaProjectBuilder builder) {
556 Collection<JavaClass> javaClasses = builder.getClasses();
557
558 if (javaClasses == null || javaClasses.size() < 1) {
559 return Collections.emptyMap();
560 }
561
562 Map<String, JavaClass> javaClassMap = new HashMap<>(javaClasses.size());
563
564 for (JavaClass javaClass : javaClasses) {
565 javaClassMap.put(javaClass.getFullyQualifiedName(), javaClass);
566 }
567
568 return javaClassMap;
569 }
570
571 protected void extendJavaProjectBuilderWithSourcesJar(
572 JavaProjectBuilder builder, Artifact artifact, PluginToolsRequest request, String classifier)
573 throws ExtractionException {
574 try {
575 org.eclipse.aether.artifact.Artifact sourcesArtifact = new DefaultArtifact(
576 artifact.getGroupId(),
577 artifact.getArtifactId(),
578 classifier,
579 artifact.getArtifactHandler().getExtension(),
580 artifact.getVersion());
581
582 ArtifactRequest resolveRequest =
583 new ArtifactRequest(sourcesArtifact, request.getProject().getRemoteProjectRepositories(), null);
584 try {
585 ArtifactResult result = repositorySystem.resolveArtifact(request.getRepoSession(), resolveRequest);
586 sourcesArtifact = result.getArtifact();
587 } catch (ArtifactResolutionException e) {
588 String message = "Unable to get sources artifact for " + artifact.getId()
589 + ". Some javadoc tags (@since, @deprecated and comments) won't be used";
590 if (getLogger().isDebugEnabled()) {
591 getLogger().warn(message, e);
592 } else {
593 getLogger().warn(message);
594 }
595 return;
596 }
597
598 if (sourcesArtifact.getFile() == null || !sourcesArtifact.getFile().exists()) {
599
600 return;
601 }
602
603 if (sourcesArtifact.getFile().isFile()) {
604
605 File extractDirectory = new File(
606 request.getProject().getBuild().getDirectory(),
607 "maven-plugin-plugin-sources/" + sourcesArtifact.getGroupId() + "/"
608 + sourcesArtifact.getArtifactId() + "/" + sourcesArtifact.getVersion()
609 + "/" + sourcesArtifact.getClassifier());
610 extractDirectory.mkdirs();
611
612 UnArchiver unArchiver = archiverManager.getUnArchiver("jar");
613 unArchiver.setSourceFile(sourcesArtifact.getFile());
614 unArchiver.setDestDirectory(extractDirectory);
615 unArchiver.extract();
616
617 extendJavaProjectBuilder(builder, Arrays.asList(extractDirectory), request.getDependencies());
618 } else if (sourcesArtifact.getFile().isDirectory()) {
619 extendJavaProjectBuilder(builder, Arrays.asList(sourcesArtifact.getFile()), request.getDependencies());
620 }
621 } catch (ArchiverException | NoSuchArchiverException e) {
622 throw new ExtractionException(e.getMessage(), e);
623 }
624 }
625
626 private void extendJavaProjectBuilder(JavaProjectBuilder builder, final MavenProject project) {
627 List<File> sources = new ArrayList<>();
628
629 for (String source : project.getCompileSourceRoots()) {
630 sources.add(new File(source));
631 }
632
633
634 File generatedPlugin = new File(project.getBasedir(), "target/generated-sources/plugin");
635 if (!project.getCompileSourceRoots().contains(generatedPlugin.getAbsolutePath()) && generatedPlugin.exists()) {
636 sources.add(generatedPlugin);
637 }
638 extendJavaProjectBuilder(builder, sources, project.getArtifacts());
639 }
640
641 private void extendJavaProjectBuilder(
642 JavaProjectBuilder builder, List<File> sourceDirectories, Set<Artifact> artifacts) {
643
644
645 List<URL> urls = new ArrayList<>(artifacts.size());
646 for (Artifact artifact : artifacts) {
647 try {
648 urls.add(artifact.getFile().toURI().toURL());
649 } catch (MalformedURLException e) {
650
651 }
652 }
653 builder.addClassLoader(new URLClassLoader(urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader()));
654
655 for (File source : sourceDirectories) {
656 builder.addSourceTree(source);
657 }
658 }
659
660 private List<MojoDescriptor> toMojoDescriptors(
661 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses, PluginDescriptor pluginDescriptor)
662 throws InvalidPluginDescriptorException {
663 List<MojoDescriptor> mojoDescriptors = new ArrayList<>(mojoAnnotatedClasses.size());
664 for (MojoAnnotatedClass mojoAnnotatedClass : mojoAnnotatedClasses.values()) {
665
666 if (mojoAnnotatedClass.getMojo() == null) {
667 continue;
668 }
669
670 ExtendedMojoDescriptor mojoDescriptor = new ExtendedMojoDescriptor(true);
671
672
673
674 mojoDescriptor.setImplementation(mojoAnnotatedClass.getClassName());
675 mojoDescriptor.setLanguage("java");
676
677 mojoDescriptor.setV4Api(mojoAnnotatedClass.isV4Api());
678
679 MojoAnnotationContent mojo = mojoAnnotatedClass.getMojo();
680
681 mojoDescriptor.setDescription(mojo.getDescription());
682 mojoDescriptor.setSince(mojo.getSince());
683 mojo.setDeprecated(mojo.getDeprecated());
684
685 mojoDescriptor.setProjectRequired(mojo.requiresProject());
686
687 mojoDescriptor.setRequiresReports(mojo.requiresReports());
688
689 mojoDescriptor.setComponentConfigurator(mojo.configurator());
690
691 mojoDescriptor.setInheritedByDefault(mojo.inheritByDefault());
692
693 mojoDescriptor.setInstantiationStrategy(mojo.instantiationStrategy().id());
694
695 mojoDescriptor.setAggregator(mojo.aggregator());
696 mojoDescriptor.setDependencyResolutionRequired(
697 mojo.requiresDependencyResolution().id());
698 mojoDescriptor.setDependencyCollectionRequired(
699 mojo.requiresDependencyCollection().id());
700
701 mojoDescriptor.setDirectInvocationOnly(mojo.requiresDirectInvocation());
702 mojoDescriptor.setDeprecated(mojo.getDeprecated());
703 mojoDescriptor.setThreadSafe(mojo.threadSafe());
704
705 MojoAnnotatedClass mojoAnnotatedClassWithExecute =
706 findClassWithExecuteAnnotationInParentHierarchy(mojoAnnotatedClass, mojoAnnotatedClasses);
707 if (mojoAnnotatedClassWithExecute != null && mojoAnnotatedClassWithExecute.getExecute() != null) {
708 ExecuteAnnotationContent execute = mojoAnnotatedClassWithExecute.getExecute();
709 mojoDescriptor.setExecuteGoal(execute.goal());
710 mojoDescriptor.setExecuteLifecycle(execute.lifecycle());
711 if (execute.phase() != null) {
712 mojoDescriptor.setExecutePhase(execute.phase().id());
713 if (StringUtils.isNotEmpty(execute.customPhase())) {
714 throw new InvalidPluginDescriptorException(
715 "@Execute annotation must only use either 'phase' "
716 + "or 'customPhase' but not both. Both are used though on "
717 + mojoAnnotatedClassWithExecute.getClassName(),
718 null);
719 }
720 } else if (StringUtils.isNotEmpty(execute.customPhase())) {
721 mojoDescriptor.setExecutePhase(execute.customPhase());
722 }
723 }
724
725 mojoDescriptor.setExecutionStrategy(mojo.executionStrategy());
726
727
728
729 mojoDescriptor.setGoal(mojo.name());
730 mojoDescriptor.setOnlineRequired(mojo.requiresOnline());
731
732 mojoDescriptor.setPhase(mojo.defaultPhase().id());
733
734
735 Map<String, ParameterAnnotationContent> parameters =
736 getParametersParentHierarchy(mojoAnnotatedClass, mojoAnnotatedClasses);
737
738 for (ParameterAnnotationContent parameterAnnotationContent : new TreeSet<>(parameters.values())) {
739 org.apache.maven.plugin.descriptor.Parameter parameter =
740 new org.apache.maven.plugin.descriptor.Parameter();
741 String name = StringUtils.isEmpty(parameterAnnotationContent.name())
742 ? parameterAnnotationContent.getFieldName()
743 : parameterAnnotationContent.name();
744 parameter.setName(name);
745 parameter.setAlias(parameterAnnotationContent.alias());
746 parameter.setDefaultValue(parameterAnnotationContent.defaultValue());
747 parameter.setDeprecated(parameterAnnotationContent.getDeprecated());
748 parameter.setDescription(parameterAnnotationContent.getDescription());
749 parameter.setEditable(!parameterAnnotationContent.readonly());
750 String property = parameterAnnotationContent.property();
751 if (StringUtils.contains(property, '$')
752 || StringUtils.contains(property, '{')
753 || StringUtils.contains(property, '}')) {
754 throw new InvalidParameterException(
755 "Invalid property for parameter '" + parameter.getName() + "', "
756 + "forbidden characters ${}: " + property,
757 null);
758 }
759 parameter.setExpression((property == null || property.isEmpty()) ? "" : "${" + property + "}");
760 StringBuilder type = new StringBuilder(parameterAnnotationContent.getClassName());
761 if (!parameterAnnotationContent.getTypeParameters().isEmpty()) {
762 type.append(parameterAnnotationContent.getTypeParameters().stream()
763 .collect(Collectors.joining(", ", "<", ">")));
764 }
765 parameter.setType(type.toString());
766 parameter.setSince(parameterAnnotationContent.getSince());
767 parameter.setRequired(parameterAnnotationContent.required());
768
769 mojoDescriptor.addParameter(parameter);
770 }
771
772
773 Map<String, ComponentAnnotationContent> components =
774 getComponentsParentHierarchy(mojoAnnotatedClass, mojoAnnotatedClasses);
775
776 for (ComponentAnnotationContent componentAnnotationContent : new TreeSet<>(components.values())) {
777 org.apache.maven.plugin.descriptor.Parameter parameter =
778 new org.apache.maven.plugin.descriptor.Parameter();
779 parameter.setName(componentAnnotationContent.getFieldName());
780
781
782 String expression = PluginUtils.MAVEN_COMPONENTS.get(componentAnnotationContent.getRoleClassName());
783 if (expression == null) {
784
785 parameter.setRequirement(new Requirement(
786 componentAnnotationContent.getRoleClassName(), componentAnnotationContent.hint()));
787 } else {
788
789 getLogger()
790 .warn("Deprecated @Component annotation for '" + parameter.getName() + "' field in "
791 + mojoAnnotatedClass.getClassName()
792 + ": replace with @Parameter( defaultValue = \"" + expression
793 + "\", readonly = true )");
794 parameter.setDefaultValue(expression);
795 parameter.setType(componentAnnotationContent.getRoleClassName());
796 parameter.setRequired(true);
797 }
798 parameter.setDeprecated(componentAnnotationContent.getDeprecated());
799 parameter.setSince(componentAnnotationContent.getSince());
800
801
802
803 parameter.setEditable(false);
804
805 mojoDescriptor.addParameter(parameter);
806 }
807
808 mojoDescriptor.setPluginDescriptor(pluginDescriptor);
809
810 mojoDescriptors.add(mojoDescriptor);
811 }
812 return mojoDescriptors;
813 }
814
815 protected MojoAnnotatedClass findClassWithExecuteAnnotationInParentHierarchy(
816 MojoAnnotatedClass mojoAnnotatedClass, Map<String, MojoAnnotatedClass> mojoAnnotatedClasses) {
817 if (mojoAnnotatedClass.getExecute() != null) {
818 return mojoAnnotatedClass;
819 }
820 String parentClassName = mojoAnnotatedClass.getParentClassName();
821 if (parentClassName == null || parentClassName.isEmpty()) {
822 return null;
823 }
824 MojoAnnotatedClass parent = mojoAnnotatedClasses.get(parentClassName);
825 if (parent == null) {
826 return null;
827 }
828 return findClassWithExecuteAnnotationInParentHierarchy(parent, mojoAnnotatedClasses);
829 }
830
831 protected Map<String, ParameterAnnotationContent> getParametersParentHierarchy(
832 MojoAnnotatedClass mojoAnnotatedClass, Map<String, MojoAnnotatedClass> mojoAnnotatedClasses) {
833 List<ParameterAnnotationContent> parameterAnnotationContents = new ArrayList<>();
834
835 parameterAnnotationContents =
836 getParametersParent(mojoAnnotatedClass, parameterAnnotationContents, mojoAnnotatedClasses);
837
838
839 Collections.reverse(parameterAnnotationContents);
840
841 Map<String, ParameterAnnotationContent> map = new HashMap<>(parameterAnnotationContents.size());
842
843 for (ParameterAnnotationContent parameterAnnotationContent : parameterAnnotationContents) {
844 map.put(parameterAnnotationContent.getFieldName(), parameterAnnotationContent);
845 }
846 return map;
847 }
848
849 protected List<ParameterAnnotationContent> getParametersParent(
850 MojoAnnotatedClass mojoAnnotatedClass,
851 List<ParameterAnnotationContent> parameterAnnotationContents,
852 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses) {
853 parameterAnnotationContents.addAll(mojoAnnotatedClass.getParameters().values());
854 String parentClassName = mojoAnnotatedClass.getParentClassName();
855 if (parentClassName != null) {
856 MojoAnnotatedClass parent = mojoAnnotatedClasses.get(parentClassName);
857 if (parent != null) {
858 return getParametersParent(parent, parameterAnnotationContents, mojoAnnotatedClasses);
859 }
860 }
861 return parameterAnnotationContents;
862 }
863
864 protected Map<String, ComponentAnnotationContent> getComponentsParentHierarchy(
865 MojoAnnotatedClass mojoAnnotatedClass, Map<String, MojoAnnotatedClass> mojoAnnotatedClasses) {
866 List<ComponentAnnotationContent> componentAnnotationContents = new ArrayList<>();
867
868 componentAnnotationContents =
869 getComponentParent(mojoAnnotatedClass, componentAnnotationContents, mojoAnnotatedClasses);
870
871
872 Collections.reverse(componentAnnotationContents);
873
874 Map<String, ComponentAnnotationContent> map = new HashMap<>(componentAnnotationContents.size());
875
876 for (ComponentAnnotationContent componentAnnotationContent : componentAnnotationContents) {
877 map.put(componentAnnotationContent.getFieldName(), componentAnnotationContent);
878 }
879 return map;
880 }
881
882 protected List<ComponentAnnotationContent> getComponentParent(
883 MojoAnnotatedClass mojoAnnotatedClass,
884 List<ComponentAnnotationContent> componentAnnotationContents,
885 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses) {
886 componentAnnotationContents.addAll(mojoAnnotatedClass.getComponents().values());
887 String parentClassName = mojoAnnotatedClass.getParentClassName();
888 if (parentClassName != null) {
889 MojoAnnotatedClass parent = mojoAnnotatedClasses.get(parentClassName);
890 if (parent != null) {
891 return getComponentParent(parent, componentAnnotationContents, mojoAnnotatedClasses);
892 }
893 }
894 return componentAnnotationContents;
895 }
896
897 protected MavenProject getFromProjectReferences(Artifact artifact, MavenProject project) {
898 if (project.getProjectReferences() == null
899 || project.getProjectReferences().isEmpty()) {
900 return null;
901 }
902 Collection<MavenProject> mavenProjects = project.getProjectReferences().values();
903 for (MavenProject mavenProject : mavenProjects) {
904 if (Objects.equals(mavenProject.getId(), artifact.getId())) {
905 return mavenProject;
906 }
907 }
908 return null;
909 }
910 }