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