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<MavenProject>();
134
135        // if we need to scan sources from external artifacts
136        Set<Artifact> externalArtifacts = new HashSet<Artifact>();
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<String, ParameterAnnotationContent>( 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<String, JavaField>();
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<File>();
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<URL>( 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<String, JavaClass>( 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<MojoDescriptor>( 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<ParameterAnnotationContent>(
550                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<ComponentAnnotationContent>(
585                components.values() ) )
586            {
587                org.apache.maven.plugin.descriptor.Parameter parameter =
588                    new org.apache.maven.plugin.descriptor.Parameter();
589                parameter.setName( componentAnnotationContent.getFieldName() );
590
591                // recognize Maven-injected objects as components annotations instead of parameters
592                String expression = PluginUtils.MAVEN_COMPONENTS.get( componentAnnotationContent.getRoleClassName() );
593                if ( expression == null )
594                {
595                    // normal component
596                    parameter.setRequirement( new Requirement( componentAnnotationContent.getRoleClassName(),
597                                                               componentAnnotationContent.hint() ) );
598                }
599                else
600                {
601                    // not a component but a Maven object to be transformed into an expression/property: deprecated
602                    getLogger().warn( "Deprecated @Component annotation for '" + parameter.getName() + "' field in "
603                                          + mojoAnnotatedClass.getClassName()
604                                          + ": replace with @Parameter( defaultValue = \"" + expression
605                                          + "\", readonly = true )" );
606                    parameter.setDefaultValue( expression );
607                    parameter.setType( componentAnnotationContent.getRoleClassName() );
608                    parameter.setRequired( true );
609                }
610                parameter.setDeprecated( componentAnnotationContent.getDeprecated() );
611                parameter.setSince( componentAnnotationContent.getSince() );
612
613                // same behaviour as JavaMojoDescriptorExtractor
614                //parameter.setRequired( ... );
615                parameter.setEditable( false );
616
617                mojoDescriptor.addParameter( parameter );
618            }
619
620            mojoDescriptor.setPluginDescriptor( pluginDescriptor );
621
622            mojoDescriptors.add( mojoDescriptor );
623        }
624        return mojoDescriptors;
625    }
626
627    protected ExecuteAnnotationContent findExecuteInParentHierarchy( MojoAnnotatedClass mojoAnnotatedClass,
628                                                                 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
629    {
630        if ( mojoAnnotatedClass.getExecute() != null )
631        {
632            return mojoAnnotatedClass.getExecute();
633        }
634        String parentClassName = mojoAnnotatedClass.getParentClassName();
635        if ( StringUtils.isEmpty( parentClassName ) )
636        {
637            return null;
638        }
639        MojoAnnotatedClass parent = mojoAnnotatedClasses.get( parentClassName );
640        if ( parent == null )
641        {
642            return null;
643        }
644        return findExecuteInParentHierarchy( parent, mojoAnnotatedClasses );
645    }
646
647
648    protected Map<String, ParameterAnnotationContent> getParametersParentHierarchy(
649        MojoAnnotatedClass mojoAnnotatedClass, Map<String, ParameterAnnotationContent> parameters,
650        Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
651    {
652        List<ParameterAnnotationContent> parameterAnnotationContents = new ArrayList<ParameterAnnotationContent>();
653
654        parameterAnnotationContents =
655            getParametersParent( mojoAnnotatedClass, parameterAnnotationContents, mojoAnnotatedClasses );
656
657        // move to parent first to build the Map
658        Collections.reverse( parameterAnnotationContents );
659
660        Map<String, ParameterAnnotationContent> map =
661            new HashMap<String, ParameterAnnotationContent>( parameterAnnotationContents.size() );
662
663        for ( ParameterAnnotationContent parameterAnnotationContent : parameterAnnotationContents )
664        {
665            map.put( parameterAnnotationContent.getFieldName(), parameterAnnotationContent );
666        }
667        return map;
668    }
669
670    protected List<ParameterAnnotationContent> getParametersParent( MojoAnnotatedClass mojoAnnotatedClass,
671                                                        List<ParameterAnnotationContent> parameterAnnotationContents,
672                                                        Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
673    {
674        parameterAnnotationContents.addAll( mojoAnnotatedClass.getParameters().values() );
675        String parentClassName = mojoAnnotatedClass.getParentClassName();
676        if ( parentClassName != null )
677        {
678            MojoAnnotatedClass parent = mojoAnnotatedClasses.get( parentClassName );
679            if ( parent != null )
680            {
681                return getParametersParent( parent, parameterAnnotationContents, mojoAnnotatedClasses );
682            }
683        }
684        return parameterAnnotationContents;
685    }
686
687    protected Map<String, ComponentAnnotationContent> getComponentsParentHierarchy(
688        MojoAnnotatedClass mojoAnnotatedClass, Map<String, ComponentAnnotationContent> components,
689        Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
690    {
691        List<ComponentAnnotationContent> componentAnnotationContents = new ArrayList<ComponentAnnotationContent>();
692
693        componentAnnotationContents =
694            getComponentParent( mojoAnnotatedClass, componentAnnotationContents, mojoAnnotatedClasses );
695
696        // move to parent first to build the Map
697        Collections.reverse( componentAnnotationContents );
698
699        Map<String, ComponentAnnotationContent> map =
700            new HashMap<String, ComponentAnnotationContent>( componentAnnotationContents.size() );
701
702        for ( ComponentAnnotationContent componentAnnotationContent : componentAnnotationContents )
703        {
704            map.put( componentAnnotationContent.getFieldName(), componentAnnotationContent );
705        }
706        return map;
707    }
708
709    protected List<ComponentAnnotationContent> getComponentParent( MojoAnnotatedClass mojoAnnotatedClass,
710                                                       List<ComponentAnnotationContent> componentAnnotationContents,
711                                                       Map<String, MojoAnnotatedClass> mojoAnnotatedClasses )
712    {
713        componentAnnotationContents.addAll( mojoAnnotatedClass.getComponents().values() );
714        String parentClassName = mojoAnnotatedClass.getParentClassName();
715        if ( parentClassName != null )
716        {
717            MojoAnnotatedClass parent = mojoAnnotatedClasses.get( parentClassName );
718            if ( parent != null )
719            {
720                return getComponentParent( parent, componentAnnotationContents, mojoAnnotatedClasses );
721            }
722        }
723        return componentAnnotationContents;
724    }
725
726    protected MavenProject getFromProjectReferences( Artifact artifact, MavenProject project )
727    {
728        if ( project.getProjectReferences() == null || project.getProjectReferences().isEmpty() )
729        {
730            return null;
731        }
732        @SuppressWarnings( "unchecked" ) Collection<MavenProject> mavenProjects =
733            project.getProjectReferences().values();
734        for ( MavenProject mavenProject : mavenProjects )
735        {
736            if ( StringUtils.equals( mavenProject.getId(), artifact.getId() ) )
737            {
738                return mavenProject;
739            }
740        }
741        return null;
742    }
743
744}