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 java.io.File;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.net.URLClassLoader;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Objects;
35  import java.util.Set;
36  import java.util.TreeMap;
37  import java.util.TreeSet;
38  
39  import org.apache.maven.artifact.Artifact;
40  import org.apache.maven.artifact.factory.ArtifactFactory;
41  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
42  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
43  import org.apache.maven.artifact.resolver.ArtifactResolver;
44  import org.apache.maven.plugin.descriptor.DuplicateParameterException;
45  import org.apache.maven.plugin.descriptor.InvalidParameterException;
46  import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
47  import org.apache.maven.plugin.descriptor.MojoDescriptor;
48  import org.apache.maven.plugin.descriptor.PluginDescriptor;
49  import org.apache.maven.plugin.descriptor.Requirement;
50  import org.apache.maven.project.MavenProject;
51  import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
52  import org.apache.maven.tools.plugin.PluginToolsRequest;
53  import org.apache.maven.tools.plugin.extractor.ExtractionException;
54  import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
55  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent;
56  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent;
57  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent;
58  import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent;
59  import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass;
60  import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScanner;
61  import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScannerRequest;
62  import org.apache.maven.tools.plugin.util.PluginUtils;
63  import org.codehaus.plexus.archiver.UnArchiver;
64  import org.codehaus.plexus.archiver.manager.ArchiverManager;
65  import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
66  import org.codehaus.plexus.component.annotations.Component;
67  import org.codehaus.plexus.logging.AbstractLogEnabled;
68  import org.codehaus.plexus.util.StringUtils;
69  
70  import com.thoughtworks.qdox.JavaProjectBuilder;
71  import com.thoughtworks.qdox.library.SortedClassLibraryBuilder;
72  import com.thoughtworks.qdox.model.DocletTag;
73  import com.thoughtworks.qdox.model.JavaClass;
74  import com.thoughtworks.qdox.model.JavaField;
75  
76  /**
77   * JavaMojoDescriptorExtractor, a MojoDescriptor extractor to read descriptors from java classes with annotations.
78   * Notice that source files are also parsed to get description, since and deprecation information.
79   *
80   * @author Olivier Lamy
81   * @since 3.0
82   */
83  @Component( role = MojoDescriptorExtractor.class, hint = "java-annotations" )
84  public class JavaAnnotationsMojoDescriptorExtractor
85      extends AbstractLogEnabled
86      implements MojoDescriptorExtractor
87  {
88  
89      @org.codehaus.plexus.component.annotations.Requirement
90      private MojoAnnotationsScanner mojoAnnotationsScanner;
91  
92      @org.codehaus.plexus.component.annotations.Requirement
93      private ArtifactResolver artifactResolver;
94  
95      @org.codehaus.plexus.component.annotations.Requirement
96      private ArtifactFactory artifactFactory;
97  
98      @org.codehaus.plexus.component.annotations.Requirement
99      private ArchiverManager archiverManager;
100 
101     @Override
102     public List<MojoDescriptor> execute( PluginToolsRequest request )
103         throws ExtractionException, InvalidPluginDescriptorException
104     {
105         Map<String, MojoAnnotatedClass> mojoAnnotatedClasses = scanAnnotations( request );
106 
107         Map<String, JavaClass> javaClassesMap = scanJavadoc( request, mojoAnnotatedClasses.values() );
108 
109         populateDataFromJavadoc( mojoAnnotatedClasses, javaClassesMap );
110 
111         return toMojoDescriptors( mojoAnnotatedClasses, request.getPluginDescriptor() );
112     }
113 
114     private Map<String, MojoAnnotatedClass> scanAnnotations( PluginToolsRequest request )
115         throws ExtractionException
116     {
117         MojoAnnotationsScannerRequest mojoAnnotationsScannerRequest = new MojoAnnotationsScannerRequest();
118 
119         File output = new File( request.getProject().getBuild().getOutputDirectory() );
120         mojoAnnotationsScannerRequest.setClassesDirectories( Arrays.asList( output ) );
121 
122         mojoAnnotationsScannerRequest.setDependencies( request.getDependencies() );
123 
124         mojoAnnotationsScannerRequest.setProject( request.getProject() );
125 
126         return mojoAnnotationsScanner.scan( mojoAnnotationsScannerRequest );
127     }
128 
129     private Map<String, JavaClass> scanJavadoc( PluginToolsRequest request,
130                                                 Collection<MojoAnnotatedClass> mojoAnnotatedClasses )
131         throws ExtractionException
132     {
133         // found artifact from reactors to scan sources
134         // we currently only scan sources from reactors
135         List<MavenProject> mavenProjects = new ArrayList<>();
136 
137         // if we need to scan sources from external artifacts
138         Set<Artifact> externalArtifacts = new HashSet<>();
139 
140         for ( MojoAnnotatedClass mojoAnnotatedClass : mojoAnnotatedClasses )
141         {
142             if ( Objects.equals( mojoAnnotatedClass.getArtifact().getArtifactId(),
143                                      request.getProject().getArtifact().getArtifactId() ) )
144             {
145                 continue;
146             }
147 
148             if ( !isMojoAnnnotatedClassCandidate( mojoAnnotatedClass ) )
149             {
150                 // we don't scan sources for classes without mojo annotations
151                 continue;
152             }
153 
154             MavenProject mavenProject =
155                 getFromProjectReferences( mojoAnnotatedClass.getArtifact(), request.getProject() );
156 
157             if ( mavenProject != null )
158             {
159                 mavenProjects.add( mavenProject );
160             }
161             else
162             {
163                 externalArtifacts.add( mojoAnnotatedClass.getArtifact() );
164             }
165         }
166 
167         Map<String, JavaClass> javaClassesMap = new HashMap<String, JavaClass>();
168 
169         // try to get artifact with sources classifier, extract somewhere then scan for @since, @deprecated
170         for ( Artifact artifact : externalArtifacts )
171         {
172             // parameter for test-sources too ?? olamy I need that for it test only
173             if ( StringUtils.equalsIgnoreCase( "tests", artifact.getClassifier() ) )
174             {
175                 javaClassesMap.putAll( discoverClassesFromSourcesJar( artifact, request, "test-sources" ) );
176             }
177             else
178             {
179                 javaClassesMap.putAll( discoverClassesFromSourcesJar( artifact, request, "sources" ) );
180             }
181 
182         }
183 
184         for ( MavenProject mavenProject : mavenProjects )
185         {
186             javaClassesMap.putAll( discoverClasses( request.getEncoding(), mavenProject ) );
187         }
188 
189         javaClassesMap.putAll( discoverClasses( request ) );
190 
191         return javaClassesMap;
192     }
193 
194     private boolean isMojoAnnnotatedClassCandidate( MojoAnnotatedClass mojoAnnotatedClass )
195     {
196         return mojoAnnotatedClass != null && mojoAnnotatedClass.hasAnnotations();
197     }
198 
199     protected Map<String, JavaClass> discoverClassesFromSourcesJar( Artifact artifact, PluginToolsRequest request,
200                                                                     String classifier )
201         throws ExtractionException
202     {
203         try
204         {
205             Artifact sourcesArtifact =
206                 artifactFactory.createArtifactWithClassifier( artifact.getGroupId(), artifact.getArtifactId(),
207                                                               artifact.getVersion(), artifact.getType(), classifier );
208 
209             artifactResolver.resolve( sourcesArtifact, request.getRemoteRepos(), request.getLocal() );
210 
211             if ( sourcesArtifact.getFile() == null || !sourcesArtifact.getFile().exists() )
212             {
213                 // could not get artifact sources
214                 return Collections.emptyMap();
215             }
216 
217             // extract sources to target/maven-plugin-plugin-sources/${groupId}/${artifact}/sources
218             File extractDirectory = new File( request.getProject().getBuild().getDirectory(),
219                                               "maven-plugin-plugin-sources/" + sourcesArtifact.getGroupId() + "/"
220                                                   + sourcesArtifact.getArtifactId() + "/" + sourcesArtifact.getVersion()
221                                                   + "/" + sourcesArtifact.getClassifier() );
222             extractDirectory.mkdirs();
223 
224             UnArchiver unArchiver = archiverManager.getUnArchiver( "jar" );
225             unArchiver.setSourceFile( sourcesArtifact.getFile() );
226             unArchiver.setDestDirectory( extractDirectory );
227             unArchiver.extract();
228 
229             return discoverClasses( request.getEncoding(), Arrays.asList( extractDirectory ), 
230                                     request.getDependencies() );
231         }
232         catch ( ArtifactResolutionException e )
233         {
234             throw new ExtractionException( e.getMessage(), e );
235         }
236         catch ( ArtifactNotFoundException e )
237         {
238             //throw new ExtractionException( e.getMessage(), e );
239             getLogger().debug( "skip ArtifactNotFoundException:" + e.getMessage() );
240             getLogger().warn(
241                 "Unable to get sources artifact for " + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
242                     + artifact.getVersion() + ". Some javadoc tags (@since, @deprecated and comments) won't be used" );
243             return Collections.emptyMap();
244         }
245         catch ( NoSuchArchiverException e )
246         {
247             throw new ExtractionException( e.getMessage(), e );
248         }
249     }
250 
251     /**
252      * from sources scan to get @since and @deprecated and description of classes and fields.
253      *
254      * @param mojoAnnotatedClasses
255      * @param javaClassesMap
256      */
257     protected void populateDataFromJavadoc( Map<String, MojoAnnotatedClass> mojoAnnotatedClasses,
258                                             Map<String, JavaClass> javaClassesMap )
259     {
260 
261         for ( Map.Entry<String, MojoAnnotatedClass> entry : mojoAnnotatedClasses.entrySet() )
262         {
263             JavaClass javaClass = javaClassesMap.get( entry.getKey() );
264             if ( javaClass == null )
265             {
266                 continue;
267             }
268 
269             // populate class-level content
270             MojoAnnotationContent mojoAnnotationContent = entry.getValue().getMojo();
271             if ( mojoAnnotationContent != null )
272             {
273                 mojoAnnotationContent.setDescription( javaClass.getComment() );
274 
275                 DocletTag since = findInClassHierarchy( javaClass, "since" );
276                 if ( since != null )
277                 {
278                     mojoAnnotationContent.setSince( since.getValue() );
279                 }
280 
281                 DocletTag deprecated = findInClassHierarchy( javaClass, "deprecated" );
282                 if ( deprecated != null )
283                 {
284                     mojoAnnotationContent.setDeprecated( deprecated.getValue() );
285                 }
286             }
287 
288             Map<String, JavaField> fieldsMap = extractFieldParameterTags( javaClass, javaClassesMap );
289 
290             // populate parameters
291             Map<String, ParameterAnnotationContent> parameters =
292                 getParametersParentHierarchy( entry.getValue(), new HashMap<String, ParameterAnnotationContent>(),
293                                               mojoAnnotatedClasses );
294             parameters = new TreeMap<>( parameters );
295             for ( Map.Entry<String, ParameterAnnotationContent> parameter : parameters.entrySet() )
296             {
297                 JavaField javaField = fieldsMap.get( parameter.getKey() );
298                 if ( javaField == null )
299                 {
300                     continue;
301                 }
302 
303                 ParameterAnnotationContent parameterAnnotationContent = parameter.getValue();
304                 parameterAnnotationContent.setDescription( javaField.getComment() );
305 
306                 DocletTag deprecated = javaField.getTagByName( "deprecated" );
307                 if ( deprecated != null )
308                 {
309                     parameterAnnotationContent.setDeprecated( deprecated.getValue() );
310                 }
311 
312                 DocletTag since = javaField.getTagByName( "since" );
313                 if ( since != null )
314                 {
315                     parameterAnnotationContent.setSince( since.getValue() );
316                 }
317             }
318 
319             // populate components
320             Map<String, ComponentAnnotationContent> components = entry.getValue().getComponents();
321             for ( Map.Entry<String, ComponentAnnotationContent> component : components.entrySet() )
322             {
323                 JavaField javaField = fieldsMap.get( component.getKey() );
324                 if ( javaField == null )
325                 {
326                     continue;
327                 }
328 
329                 ComponentAnnotationContent componentAnnotationContent = component.getValue();
330                 componentAnnotationContent.setDescription( javaField.getComment() );
331 
332                 DocletTag deprecated = javaField.getTagByName( "deprecated" );
333                 if ( deprecated != null )
334                 {
335                     componentAnnotationContent.setDeprecated( deprecated.getValue() );
336                 }
337 
338                 DocletTag since = javaField.getTagByName( "since" );
339                 if ( since != null )
340                 {
341                     componentAnnotationContent.setSince( since.getValue() );
342                 }
343             }
344 
345         }
346 
347     }
348 
349     /**
350      * @param javaClass not null
351      * @param tagName   not null
352      * @return docletTag instance
353      */
354     private DocletTag findInClassHierarchy( JavaClass javaClass, String tagName )
355     {
356         DocletTag tag = javaClass.getTagByName( tagName );
357 
358         if ( tag == null )
359         {
360             JavaClass superClass = javaClass.getSuperJavaClass();
361 
362             if ( superClass != null )
363             {
364                 tag = findInClassHierarchy( superClass, tagName );
365             }
366         }
367 
368         return tag;
369     }
370 
371     /**
372      * extract fields that are either parameters or components.
373      *
374      * @param javaClass not null
375      * @return map with Mojo parameters names as keys
376      */
377     private Map<String, JavaField> extractFieldParameterTags( JavaClass javaClass,
378                                                               Map<String, JavaClass> javaClassesMap )
379     {
380         Map<String, JavaField> rawParams = new TreeMap<String, com.thoughtworks.qdox.model.JavaField>();
381 
382         // we have to add the parent fields first, so that they will be overwritten by the local fields if
383         // that actually happens...
384         JavaClass superClass = javaClass.getSuperJavaClass();
385 
386         if ( superClass != null )
387         {
388             if ( superClass.getFields().size() > 0 )
389             {
390                 rawParams = extractFieldParameterTags( superClass, javaClassesMap );
391             }
392             // maybe sources comes from scan of sources artifact
393             superClass = javaClassesMap.get( superClass.getFullyQualifiedName() );
394             if ( superClass != null )
395             {
396                 rawParams = extractFieldParameterTags( superClass, javaClassesMap );
397             }
398         }
399         else
400         {
401 
402             rawParams = new TreeMap<>();
403         }
404 
405         for ( JavaField field : javaClass.getFields() )
406         {
407             rawParams.put( field.getName(), field );
408         }
409         
410         return rawParams;
411     }
412 
413     protected Map<String, JavaClass> discoverClasses( final PluginToolsRequest request )
414     {
415         return discoverClasses( request.getEncoding(), request.getProject() );
416     }
417 
418     protected Map<String, JavaClass> discoverClasses( final String encoding, final MavenProject project )
419     {
420         List<File> sources = new ArrayList<>();
421 
422         for ( String source : project.getCompileSourceRoots() )
423         {
424             sources.add( new File( source ) );
425         }
426 
427         // TODO be more dynamic
428         File generatedPlugin = new File( project.getBasedir(), "target/generated-sources/plugin" );
429         if ( !project.getCompileSourceRoots().contains( generatedPlugin.getAbsolutePath() )
430             && generatedPlugin.exists() )
431         {
432             sources.add( generatedPlugin );
433         }
434 
435         return discoverClasses( encoding, sources,  project.getArtifacts() );
436     }
437 
438     protected Map<String, JavaClass> discoverClasses( final String encoding, List<File> sourceDirectories,
439                                                       Set<Artifact> artifacts )
440     {
441         JavaProjectBuilder builder = new JavaProjectBuilder( new SortedClassLibraryBuilder() );
442         builder.setEncoding( encoding );
443 
444         // Build isolated Classloader with only the artifacts of the project (none of this plugin) 
445         List<URL> urls = new ArrayList<>( artifacts.size() );
446         for ( Artifact artifact : artifacts )
447         {
448             try
449             {
450                 urls.add( artifact.getFile().toURI().toURL() );
451             }
452             catch ( MalformedURLException e )
453             {
454                 // noop
455             }
456         }
457         builder.addClassLoader( new URLClassLoader( urls.toArray( new URL[0] ), ClassLoader.getSystemClassLoader() ) );
458 
459         for ( File source : sourceDirectories )
460         {
461             builder.addSourceTree( source );
462         }
463 
464         Collection<JavaClass> javaClasses = builder.getClasses();
465 
466         if ( javaClasses == null || javaClasses.size() < 1 )
467         {
468             return Collections.emptyMap();
469         }
470 
471         Map<String, JavaClass> javaClassMap = new HashMap<>( javaClasses.size() );
472 
473         for ( JavaClass javaClass : javaClasses )
474         {
475             javaClassMap.put( javaClass.getFullyQualifiedName(), javaClass );
476         }
477 
478         return javaClassMap;
479     }
480 
481     private List<MojoDescriptor> toMojoDescriptors( Map<String, MojoAnnotatedClass> mojoAnnotatedClasses,
482                                                     PluginDescriptor pluginDescriptor )
483         throws DuplicateParameterException, InvalidParameterException
484     {
485         List<MojoDescriptor> mojoDescriptors = new ArrayList<>( mojoAnnotatedClasses.size() );
486         for ( MojoAnnotatedClass mojoAnnotatedClass : mojoAnnotatedClasses.values() )
487         {
488             // no mojo so skip it
489             if ( mojoAnnotatedClass.getMojo() == null )
490             {
491                 continue;
492             }
493 
494             ExtendedMojoDescriptor mojoDescriptor = new ExtendedMojoDescriptor();
495 
496             //mojoDescriptor.setRole( mojoAnnotatedClass.getClassName() );
497             //mojoDescriptor.setRoleHint( "default" );
498             mojoDescriptor.setImplementation( mojoAnnotatedClass.getClassName() );
499             mojoDescriptor.setLanguage( "java" );
500 
501             MojoAnnotationContent mojo = mojoAnnotatedClass.getMojo();
502 
503             mojoDescriptor.setDescription( mojo.getDescription() );
504             mojoDescriptor.setSince( mojo.getSince() );
505             mojo.setDeprecated( mojo.getDeprecated() );
506 
507             mojoDescriptor.setProjectRequired( mojo.requiresProject() );
508 
509             mojoDescriptor.setRequiresReports( mojo.requiresReports() );
510 
511             mojoDescriptor.setComponentConfigurator( mojo.configurator() );
512 
513             mojoDescriptor.setInheritedByDefault( mojo.inheritByDefault() );
514 
515             mojoDescriptor.setInstantiationStrategy( mojo.instantiationStrategy().id() );
516 
517             mojoDescriptor.setAggregator( mojo.aggregator() );
518             mojoDescriptor.setDependencyResolutionRequired( mojo.requiresDependencyResolution().id() );
519             mojoDescriptor.setDependencyCollectionRequired( mojo.requiresDependencyCollection().id() );
520 
521             mojoDescriptor.setDirectInvocationOnly( mojo.requiresDirectInvocation() );
522             mojoDescriptor.setDeprecated( mojo.getDeprecated() );
523             mojoDescriptor.setThreadSafe( mojo.threadSafe() );
524 
525             ExecuteAnnotationContent execute = findExecuteInParentHierarchy( mojoAnnotatedClass, mojoAnnotatedClasses );
526             if ( execute != null )
527             {
528                 mojoDescriptor.setExecuteGoal( execute.goal() );
529                 mojoDescriptor.setExecuteLifecycle( execute.lifecycle() );
530                 if ( execute.phase() != null )
531                 {
532                     mojoDescriptor.setExecutePhase( execute.phase().id() );
533                 }
534             }
535 
536             mojoDescriptor.setExecutionStrategy( mojo.executionStrategy() );
537             // ???
538             //mojoDescriptor.alwaysExecute(mojo.a)
539 
540             mojoDescriptor.setGoal( mojo.name() );
541             mojoDescriptor.setOnlineRequired( mojo.requiresOnline() );
542 
543             mojoDescriptor.setPhase( mojo.defaultPhase().id() );
544 
545             // Parameter annotations
546             Map<String, ParameterAnnotationContent> parameters =
547                 getParametersParentHierarchy( mojoAnnotatedClass, new HashMap<String, ParameterAnnotationContent>(),
548                                               mojoAnnotatedClasses );
549 
550             for ( ParameterAnnotationContent parameterAnnotationContent : new TreeSet<>( parameters.values() ) )
551             {
552                 org.apache.maven.plugin.descriptor.Parameter parameter =
553                     new org.apache.maven.plugin.descriptor.Parameter();
554                 String name =
555                     StringUtils.isEmpty( parameterAnnotationContent.name() ) ? parameterAnnotationContent.getFieldName()
556                                     : parameterAnnotationContent.name();
557                 parameter.setName( name );
558                 parameter.setAlias( parameterAnnotationContent.alias() );
559                 parameter.setDefaultValue( parameterAnnotationContent.defaultValue() );
560                 parameter.setDeprecated( parameterAnnotationContent.getDeprecated() );
561                 parameter.setDescription( parameterAnnotationContent.getDescription() );
562                 parameter.setEditable( !parameterAnnotationContent.readonly() );
563                 String property = parameterAnnotationContent.property();
564                 if ( StringUtils.contains( property, '$' ) || StringUtils.contains( property, '{' )
565                     || StringUtils.contains( property, '}' ) )
566                 {
567                     throw new InvalidParameterException(
568                         "Invalid property for parameter '" + parameter.getName() + "', " + "forbidden characters ${}: "
569                             + property, null );
570                 }
571                 parameter.setExpression( StringUtils.isEmpty( property ) ? "" : "${" + property + "}" );
572                 parameter.setType( parameterAnnotationContent.getClassName() );
573                 parameter.setSince( parameterAnnotationContent.getSince() );
574                 parameter.setRequired( parameterAnnotationContent.required() );
575 
576                 mojoDescriptor.addParameter( parameter );
577             }
578 
579             // Component annotations
580             Map<String, ComponentAnnotationContent> components =
581                 getComponentsParentHierarchy( mojoAnnotatedClass, new HashMap<String, ComponentAnnotationContent>(),
582                                               mojoAnnotatedClasses );
583 
584             for ( ComponentAnnotationContent componentAnnotationContent : new TreeSet<>( components.values() ) )
585             {
586                 org.apache.maven.plugin.descriptor.Parameter parameter =
587                     new org.apache.maven.plugin.descriptor.Parameter();
588                 parameter.setName( componentAnnotationContent.getFieldName() );
589 
590                 // recognize Maven-injected objects as components annotations instead of parameters
591                 String expression = PluginUtils.MAVEN_COMPONENTS.get( componentAnnotationContent.getRoleClassName() );
592                 if ( expression == null )
593                 {
594                     // normal component
595                     parameter.setRequirement( new Requirement( componentAnnotationContent.getRoleClassName(),
596                                                                componentAnnotationContent.hint() ) );
597                 }
598                 else
599                 {
600                     // not a component but a Maven object to be transformed into an expression/property: deprecated
601                     getLogger().warn( "Deprecated @Component annotation for '" + parameter.getName() + "' field in "
602                                           + mojoAnnotatedClass.getClassName()
603                                           + ": replace with @Parameter( defaultValue = \"" + expression
604                                           + "\", readonly = true )" );
605                     parameter.setDefaultValue( expression );
606                     parameter.setType( componentAnnotationContent.getRoleClassName() );
607                     parameter.setRequired( true );
608                 }
609                 parameter.setDeprecated( componentAnnotationContent.getDeprecated() );
610                 parameter.setSince( componentAnnotationContent.getSince() );
611 
612                 // same behaviour as JavaMojoDescriptorExtractor
613                 //parameter.setRequired( ... );
614                 parameter.setEditable( false );
615 
616                 mojoDescriptor.addParameter( parameter );
617             }
618 
619             mojoDescriptor.setPluginDescriptor( pluginDescriptor );
620 
621             mojoDescriptors.add( mojoDescriptor );
622         }
623         return mojoDescriptors;
624     }
625 
626     protected ExecuteAnnotationContent findExecuteInParentHierarchy( MojoAnnotatedClass mojoAnnotatedClass,
627                                                                  Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
628     {
629         if ( mojoAnnotatedClass.getExecute() != null )
630         {
631             return mojoAnnotatedClass.getExecute();
632         }
633         String parentClassName = mojoAnnotatedClass.getParentClassName();
634         if ( StringUtils.isEmpty( parentClassName ) )
635         {
636             return null;
637         }
638         MojoAnnotatedClass parent = mojoAnnotatedClasses.get( parentClassName );
639         if ( parent == null )
640         {
641             return null;
642         }
643         return findExecuteInParentHierarchy( parent, mojoAnnotatedClasses );
644     }
645 
646 
647     protected Map<String, ParameterAnnotationContent> getParametersParentHierarchy(
648         MojoAnnotatedClass mojoAnnotatedClass, Map<String, ParameterAnnotationContent> parameters,
649         Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
650     {
651         List<ParameterAnnotationContent> parameterAnnotationContents = new ArrayList<>();
652 
653         parameterAnnotationContents =
654             getParametersParent( mojoAnnotatedClass, parameterAnnotationContents, mojoAnnotatedClasses );
655 
656         // move to parent first to build the Map
657         Collections.reverse( parameterAnnotationContents );
658 
659         Map<String, ParameterAnnotationContent> map = new HashMap<>( parameterAnnotationContents.size() );
660 
661         for ( ParameterAnnotationContent parameterAnnotationContent : parameterAnnotationContents )
662         {
663             map.put( parameterAnnotationContent.getFieldName(), parameterAnnotationContent );
664         }
665         return map;
666     }
667 
668     protected List<ParameterAnnotationContent> getParametersParent( MojoAnnotatedClass mojoAnnotatedClass,
669                                                         List<ParameterAnnotationContent> parameterAnnotationContents,
670                                                         Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
671     {
672         parameterAnnotationContents.addAll( mojoAnnotatedClass.getParameters().values() );
673         String parentClassName = mojoAnnotatedClass.getParentClassName();
674         if ( parentClassName != null )
675         {
676             MojoAnnotatedClass parent = mojoAnnotatedClasses.get( parentClassName );
677             if ( parent != null )
678             {
679                 return getParametersParent( parent, parameterAnnotationContents, mojoAnnotatedClasses );
680             }
681         }
682         return parameterAnnotationContents;
683     }
684 
685     protected Map<String, ComponentAnnotationContent> getComponentsParentHierarchy(
686         MojoAnnotatedClass mojoAnnotatedClass, Map<String, ComponentAnnotationContent> components,
687         Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
688     {
689         List<ComponentAnnotationContent> componentAnnotationContents = new ArrayList<>();
690 
691         componentAnnotationContents =
692             getComponentParent( mojoAnnotatedClass, componentAnnotationContents, mojoAnnotatedClasses );
693 
694         // move to parent first to build the Map
695         Collections.reverse( componentAnnotationContents );
696 
697         Map<String, ComponentAnnotationContent> map = new HashMap<>( componentAnnotationContents.size() );
698 
699         for ( ComponentAnnotationContent componentAnnotationContent : componentAnnotationContents )
700         {
701             map.put( componentAnnotationContent.getFieldName(), componentAnnotationContent );
702         }
703         return map;
704     }
705 
706     protected List<ComponentAnnotationContent> getComponentParent( MojoAnnotatedClass mojoAnnotatedClass,
707                                                        List<ComponentAnnotationContent> componentAnnotationContents,
708                                                        Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
709     {
710         componentAnnotationContents.addAll( mojoAnnotatedClass.getComponents().values() );
711         String parentClassName = mojoAnnotatedClass.getParentClassName();
712         if ( parentClassName != null )
713         {
714             MojoAnnotatedClass parent = mojoAnnotatedClasses.get( parentClassName );
715             if ( parent != null )
716             {
717                 return getComponentParent( parent, componentAnnotationContents, mojoAnnotatedClasses );
718             }
719         }
720         return componentAnnotationContents;
721     }
722 
723     protected MavenProject getFromProjectReferences( Artifact artifact, MavenProject project )
724     {
725         if ( project.getProjectReferences() == null || project.getProjectReferences().isEmpty() )
726         {
727             return null;
728         }
729         Collection<MavenProject> mavenProjects = project.getProjectReferences().values();
730         for ( MavenProject mavenProject : mavenProjects )
731         {
732             if ( Objects.equals( mavenProject.getId(), artifact.getId() ) )
733             {
734                 return mavenProject;
735             }
736         }
737         return null;
738     }
739 
740 }