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