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