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