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