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