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