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