001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.tools.plugin.extractor.annotations; 020 021import javax.inject.Inject; 022import javax.inject.Named; 023import javax.inject.Singleton; 024 025import java.io.File; 026import java.net.MalformedURLException; 027import java.net.URL; 028import java.net.URLClassLoader; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collection; 032import java.util.Collections; 033import java.util.Comparator; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.List; 037import java.util.Map; 038import java.util.Objects; 039import java.util.Optional; 040import java.util.Set; 041import java.util.TreeMap; 042import java.util.TreeSet; 043import java.util.stream.Collectors; 044 045import com.thoughtworks.qdox.JavaProjectBuilder; 046import com.thoughtworks.qdox.library.SortedClassLibraryBuilder; 047import com.thoughtworks.qdox.model.DocletTag; 048import com.thoughtworks.qdox.model.JavaAnnotatedElement; 049import com.thoughtworks.qdox.model.JavaClass; 050import com.thoughtworks.qdox.model.JavaField; 051import com.thoughtworks.qdox.model.JavaMember; 052import com.thoughtworks.qdox.model.JavaMethod; 053import org.apache.maven.artifact.Artifact; 054import org.apache.maven.artifact.versioning.ComparableVersion; 055import org.apache.maven.plugin.descriptor.InvalidParameterException; 056import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException; 057import org.apache.maven.plugin.descriptor.MojoDescriptor; 058import org.apache.maven.plugin.descriptor.PluginDescriptor; 059import org.apache.maven.plugin.descriptor.Requirement; 060import org.apache.maven.project.MavenProject; 061import org.apache.maven.tools.plugin.ExtendedMojoDescriptor; 062import org.apache.maven.tools.plugin.PluginToolsRequest; 063import org.apache.maven.tools.plugin.extractor.ExtractionException; 064import org.apache.maven.tools.plugin.extractor.GroupKey; 065import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor; 066import org.apache.maven.tools.plugin.extractor.annotations.converter.ConverterContext; 067import org.apache.maven.tools.plugin.extractor.annotations.converter.JavaClassConverterContext; 068import org.apache.maven.tools.plugin.extractor.annotations.converter.JavadocBlockTagsToXhtmlConverter; 069import org.apache.maven.tools.plugin.extractor.annotations.converter.JavadocInlineTagsToXhtmlConverter; 070import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent; 071import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent; 072import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent; 073import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent; 074import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass; 075import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScanner; 076import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScannerRequest; 077import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator; 078import org.codehaus.plexus.archiver.ArchiverException; 079import org.codehaus.plexus.archiver.UnArchiver; 080import org.codehaus.plexus.archiver.manager.ArchiverManager; 081import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; 082import org.codehaus.plexus.logging.AbstractLogEnabled; 083import org.codehaus.plexus.util.StringUtils; 084import org.eclipse.aether.RepositorySystem; 085import org.eclipse.aether.artifact.DefaultArtifact; 086import org.eclipse.aether.resolution.ArtifactRequest; 087import org.eclipse.aether.resolution.ArtifactResolutionException; 088import org.eclipse.aether.resolution.ArtifactResult; 089import org.objectweb.asm.Opcodes; 090 091/** 092 * JavaMojoDescriptorExtractor, a MojoDescriptor extractor to read descriptors from java classes with annotations. 093 * Notice that source files are also parsed to get description, since and deprecation information. 094 * 095 * @author Olivier Lamy 096 * @since 3.0 097 */ 098@Named(JavaAnnotationsMojoDescriptorExtractor.NAME) 099@Singleton 100public 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 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se19/html/jvms-4.html#jvms-4.1">JVMS 4.1</a> 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; // this is the "current way" to write Java Mojos 161 } 162 163 @Override 164 public GroupKey getGroupKey() { 165 return GROUP_KEY; 166 } 167 168 /** 169 * Compares class file format versions. 170 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se19/html/jvms-4.html#jvms-4.1">JVMS 4.1</a> 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 // first compare major version ( 178 int result = Integer.compare(classVersion1 & 0x00FF, classVersion2 & 0x00FF); 179 if (result == 0) { 180 // compare minor version if major is equal 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 // found artifact from reactors to scan sources 244 // we currently only scan sources from reactors 245 List<MavenProject> mavenProjects = new ArrayList<>(); 246 247 // if we need to scan sources from external artifacts 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 // we don't scan sources for classes without mojo annotations 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 // try to get artifact with sources classifier, extract somewhere then scan for @since, @deprecated 277 for (Artifact artifact : externalArtifacts) { 278 // parameter for test-sources too ?? olamy I need that for it test only 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 * from sources scan to get @since and @deprecated and description of classes and fields. 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 // populate class-level content 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 // populate parameters 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 // populate components 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 * Returns the XHTML description from the given element. 397 * This may refer to either goal, parameter or component. 398 * @param element the element for which to generate the description 399 * @param context the context with which to call the converter 400 * @return the generated description 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 // also consider see block tags 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 // just resolve inline tags and convert to XHTML 420 return javadocInlineTagsToHtmlConverter.convert(docletTag.getValue(), context); 421 } 422 423 /** 424 * @param javaClass not null 425 * @param tagName not null 426 * @return docletTag instance 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 * extract fields that are either parameters or components. 458 * 459 * @param javaClass not null 460 * @return map with Mojo parameters names as keys 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 // we have to add the parent fields first, so that they will be overwritten by the local fields if 468 // that actually happens... 469 JavaClass superClass = javaClass.getSuperJavaClass(); 470 471 if (superClass != null) { 472 if (!superClass.getFields().isEmpty()) { 473 rawParams = extractFieldsAnnotations(superClass, javaClassesMap); 474 } 475 // maybe sources comes from scan of sources artifact 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 * extract methods that are parameters. 498 * 499 * @param javaClass not null 500 * @return map with Mojo parameters names as keys 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 // we have to add the parent methods first, so that they will be overwritten by the local methods if 508 // that actually happens... 509 JavaClass superClass = javaClass.getSuperJavaClass(); 510 511 if (superClass != null) { 512 if (!superClass.getMethods().isEmpty()) { 513 rawParams = extractMethodsAnnotations(superClass, javaClassesMap); 514 } 515 // maybe sources comes from scan of sources artifact 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 // could not get artifact sources 602 return; 603 } 604 605 if (sourcesArtifact.getFile().isFile()) { 606 // extract sources to target/maven-plugin-plugin-sources/${groupId}/${artifact}/sources 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 // TODO be more dynamic 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 // Build isolated Classloader with only the artifacts of the project (none of this plugin) 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 // noop 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 // no mojo so skip it 668 if (mojoAnnotatedClass.getMojo() == null) { 669 continue; 670 } 671 672 ExtendedMojoDescriptor mojoDescriptor = new ExtendedMojoDescriptor(true); 673 674 // mojoDescriptor.setRole( mojoAnnotatedClass.getClassName() ); 675 // mojoDescriptor.setRoleHint( "default" ); 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 // mojoDescriptor.alwaysExecute(mojo.a) 730 731 mojoDescriptor.setGoal(mojo.name()); 732 mojoDescriptor.setOnlineRequired(mojo.requiresOnline()); 733 734 mojoDescriptor.setPhase(mojo.defaultPhase().id()); 735 736 // Parameter annotations 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 // Component annotations 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 // same behaviour as JavaJavadocMojoDescriptorExtractor 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 // move to parent first to build the Map 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 // move to parent first to build the Map 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}