001package org.apache.maven.tools.plugin.extractor.annotations;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.net.MalformedURLException;
024import java.net.URL;
025import java.net.URLClassLoader;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Objects;
035import java.util.Set;
036import java.util.TreeMap;
037import java.util.TreeSet;
038
039import org.apache.maven.artifact.Artifact;
040import org.apache.maven.artifact.factory.ArtifactFactory;
041import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
042import org.apache.maven.artifact.resolver.ArtifactResolutionException;
043import org.apache.maven.artifact.resolver.ArtifactResolver;
044import org.apache.maven.plugin.descriptor.DuplicateParameterException;
045import org.apache.maven.plugin.descriptor.InvalidParameterException;
046import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
047import org.apache.maven.plugin.descriptor.MojoDescriptor;
048import org.apache.maven.plugin.descriptor.PluginDescriptor;
049import org.apache.maven.plugin.descriptor.Requirement;
050import org.apache.maven.project.MavenProject;
051import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
052import org.apache.maven.tools.plugin.PluginToolsRequest;
053import org.apache.maven.tools.plugin.extractor.ExtractionException;
054import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
055import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ComponentAnnotationContent;
056import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ExecuteAnnotationContent;
057import org.apache.maven.tools.plugin.extractor.annotations.datamodel.MojoAnnotationContent;
058import org.apache.maven.tools.plugin.extractor.annotations.datamodel.ParameterAnnotationContent;
059import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass;
060import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScanner;
061import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotationsScannerRequest;
062import org.apache.maven.tools.plugin.util.PluginUtils;
063import org.codehaus.plexus.archiver.UnArchiver;
064import org.codehaus.plexus.archiver.manager.ArchiverManager;
065import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
066import org.codehaus.plexus.component.annotations.Component;
067import org.codehaus.plexus.logging.AbstractLogEnabled;
068import org.codehaus.plexus.util.StringUtils;
069
070import com.thoughtworks.qdox.JavaProjectBuilder;
071import com.thoughtworks.qdox.library.SortedClassLibraryBuilder;
072import com.thoughtworks.qdox.model.DocletTag;
073import com.thoughtworks.qdox.model.JavaClass;
074import com.thoughtworks.qdox.model.JavaField;
075
076/**
077 * JavaMojoDescriptorExtractor, a MojoDescriptor extractor to read descriptors from java classes with annotations.
078 * Notice that source files are also parsed to get description, since and deprecation information.
079 *
080 * @author Olivier Lamy
081 * @since 3.0
082 */
083@Component( role = MojoDescriptorExtractor.class, hint = "java-annotations" )
084public class JavaAnnotationsMojoDescriptorExtractor
085    extends AbstractLogEnabled
086    implements MojoDescriptorExtractor
087{
088
089    @org.codehaus.plexus.component.annotations.Requirement
090    private MojoAnnotationsScanner mojoAnnotationsScanner;
091
092    @org.codehaus.plexus.component.annotations.Requirement
093    private ArtifactResolver artifactResolver;
094
095    @org.codehaus.plexus.component.annotations.Requirement
096    private ArtifactFactory artifactFactory;
097
098    @org.codehaus.plexus.component.annotations.Requirement
099    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}