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