001    package org.apache.maven.tools.plugin.extractor.java;
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    
022    import com.thoughtworks.qdox.JavaDocBuilder;
023    import com.thoughtworks.qdox.model.DocletTag;
024    import com.thoughtworks.qdox.model.JavaClass;
025    import com.thoughtworks.qdox.model.JavaField;
026    import com.thoughtworks.qdox.model.Type;
027    
028    import org.apache.maven.plugin.descriptor.InvalidParameterException;
029    import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
030    import org.apache.maven.plugin.descriptor.MojoDescriptor;
031    import org.apache.maven.plugin.descriptor.Parameter;
032    import org.apache.maven.plugin.descriptor.PluginDescriptor;
033    import org.apache.maven.plugin.descriptor.Requirement;
034    import org.apache.maven.project.MavenProject;
035    import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
036    import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
037    import org.apache.maven.tools.plugin.PluginToolsRequest;
038    import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
039    import org.apache.maven.tools.plugin.extractor.ExtractionException;
040    import org.apache.maven.tools.plugin.util.PluginUtils;
041    
042    import org.codehaus.plexus.component.annotations.Component;
043    import org.codehaus.plexus.logging.AbstractLogEnabled;
044    import org.codehaus.plexus.util.StringUtils;
045    
046    import java.io.File;
047    import java.util.ArrayList;
048    import java.util.List;
049    import java.util.Map;
050    import java.util.TreeMap;
051    
052    /**
053     * Extracts Mojo descriptors from <a href="http://java.sun.com/">Java</a> sources.
054     * <br/>
055     * For more information about the usage tag, have a look to:
056     * <a href="http://maven.apache.org/developers/mojo-api-specification.html">
057     * http://maven.apache.org/developers/mojo-api-specification.html</a>
058     *
059     * @todo need to add validation directives so that systems embedding maven2 can
060     * get validation directives to help users in IDEs.
061     * @version $Id: JavaMojoDescriptorExtractor.java 1353231 2012-06-24 08:36:58Z hboutemy $
062     * @see org.apache.maven.plugin.descriptor.MojoDescriptor
063     */
064    @Component( role = MojoDescriptorExtractor.class, hint = "java" )
065    public class JavaMojoDescriptorExtractor
066        extends AbstractLogEnabled
067        implements MojoDescriptorExtractor, JavaMojoAnnotation
068    {
069        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#INSTANTIATION_STRATEGY} instead of. */
070        public static final String MAVEN_PLUGIN_INSTANTIATION = JavaMojoAnnotation.INSTANTIATION_STRATEGY;
071    
072        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#CONFIGURATOR} instead of. */
073        public static final String CONFIGURATOR = JavaMojoAnnotation.CONFIGURATOR;
074    
075        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#PARAMETER} instead of. */
076        public static final String PARAMETER = JavaMojoAnnotation.PARAMETER;
077    
078        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#PARAMETER_EXPRESSION} instead of. */
079        public static final String PARAMETER_EXPRESSION = JavaMojoAnnotation.PARAMETER_EXPRESSION;
080    
081        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#PARAMETER_DEFAULT_VALUE} instead of. */
082        public static final String PARAMETER_DEFAULT_VALUE = JavaMojoAnnotation.PARAMETER_DEFAULT_VALUE;
083    
084        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#PARAMETER_ALIAS} instead of. */
085        public static final String PARAMETER_ALIAS = JavaMojoAnnotation.PARAMETER_ALIAS;
086    
087        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#SINCE} instead of. */
088        public static final String SINCE = JavaMojoAnnotation.SINCE;
089    
090        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#PARAMETER_IMPLEMENTATION} instead of. */
091        public static final String PARAMETER_IMPLEMENTATION = JavaMojoAnnotation.PARAMETER_IMPLEMENTATION;
092    
093        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#REQUIRED} instead of. */
094        public static final String REQUIRED = JavaMojoAnnotation.REQUIRED;
095    
096        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#DEPRECATED} instead of. */
097        public static final String DEPRECATED = JavaMojoAnnotation.DEPRECATED;
098    
099        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#READONLY} instead of. */
100        public static final String READONLY = JavaMojoAnnotation.READONLY;
101    
102        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#GOAL} instead of. */
103        public static final String GOAL = JavaMojoAnnotation.GOAL;
104    
105        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#PHASE} instead of. */
106        public static final String PHASE = JavaMojoAnnotation.PHASE;
107    
108        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#EXECUTE} instead of. */
109        public static final String EXECUTE = JavaMojoAnnotation.EXECUTE;
110    
111        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#EXECUTE_LIFECYCLE} instead of. */
112        public static final String EXECUTE_LIFECYCLE = JavaMojoAnnotation.EXECUTE_LIFECYCLE;
113    
114        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#EXECUTE_PHASE} instead of. */
115        public static final String EXECUTE_PHASE = JavaMojoAnnotation.EXECUTE_PHASE;
116    
117        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#EXECUTE_GOAL} instead of. */
118        public static final String EXECUTE_GOAL = JavaMojoAnnotation.EXECUTE_GOAL;
119    
120        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#DESCRIPTION} instead of. */
121        public static final String GOAL_DESCRIPTION = JavaMojoAnnotation.DESCRIPTION;
122    
123        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#REQUIRES_DEPENDENCY_RESOLUTION} instead of. */
124        public static final String GOAL_REQUIRES_DEPENDENCY_RESOLUTION = JavaMojoAnnotation.REQUIRES_DEPENDENCY_RESOLUTION;
125    
126        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#REQUIRES_PROJECT} instead of. */
127        public static final String GOAL_REQUIRES_PROJECT = JavaMojoAnnotation.REQUIRES_PROJECT;
128    
129        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#REQUIRES_REPORTS} instead of. */
130        public static final String GOAL_REQUIRES_REPORTS = JavaMojoAnnotation.REQUIRES_REPORTS;
131    
132        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#AGGREGATOR} instead of. */
133        public static final String GOAL_IS_AGGREGATOR = JavaMojoAnnotation.AGGREGATOR;
134    
135        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#REQUIRES_ONLINE} instead of. */
136        public static final String GOAL_REQUIRES_ONLINE = JavaMojoAnnotation.REQUIRES_ONLINE;
137    
138        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#INHERIT_BY_DEFAULT} instead of. */
139        public static final String GOAL_INHERIT_BY_DEFAULT = JavaMojoAnnotation.INHERIT_BY_DEFAULT;
140    
141        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#MULTI_EXECUTION_STRATEGY} instead of. */
142        public static final String GOAL_MULTI_EXECUTION_STRATEGY = JavaMojoAnnotation.MULTI_EXECUTION_STRATEGY;
143    
144        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#REQUIRES_DIRECT_INVOCATION} instead of. */
145        public static final String GOAL_REQUIRES_DIRECT_INVOCATION = JavaMojoAnnotation.REQUIRES_DIRECT_INVOCATION;
146    
147        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#COMPONENT} instead of. */
148        public static final String COMPONENT = JavaMojoAnnotation.COMPONENT;
149    
150        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#COMPONENT_ROLE} instead of. */
151        public static final String COMPONENT_ROLE = JavaMojoAnnotation.COMPONENT_ROLE;
152    
153        /** @deprecated since 2.4, use {@link JavaMojoAnnotation#COMPONENT_ROLEHINT} instead of. */
154        public static final String COMPONENT_ROLEHINT = JavaMojoAnnotation.COMPONENT_ROLEHINT;
155    
156        /**
157         * @param parameter not null
158         * @param i positive number
159         * @throws InvalidParameterException if any
160         */
161        protected void validateParameter( Parameter parameter, int i )
162            throws InvalidParameterException
163        {
164            // TODO: remove when backward compatibility is no longer an issue.
165            String name = parameter.getName();
166    
167            if ( name == null )
168            {
169                throw new InvalidParameterException( "name", i );
170            }
171    
172            // TODO: remove when backward compatibility is no longer an issue.
173            String type = parameter.getType();
174    
175            if ( type == null )
176            {
177                throw new InvalidParameterException( "type", i );
178            }
179    
180            // TODO: remove when backward compatibility is no longer an issue.
181            String description = parameter.getDescription();
182    
183            if ( description == null )
184            {
185                throw new InvalidParameterException( "description", i );
186            }
187        }
188    
189        // ----------------------------------------------------------------------
190        // Mojo descriptor creation from @tags
191        // ----------------------------------------------------------------------
192    
193        /**
194         * @param javaClass not null
195         * @return a mojo descriptor
196         * @throws InvalidPluginDescriptorException if any
197         */
198        protected MojoDescriptor createMojoDescriptor( JavaClass javaClass )
199            throws InvalidPluginDescriptorException
200        {
201            ExtendedMojoDescriptor mojoDescriptor = new ExtendedMojoDescriptor();
202            mojoDescriptor.setLanguage( "java" );
203            mojoDescriptor.setImplementation( javaClass.getFullyQualifiedName() );
204            mojoDescriptor.setDescription( javaClass.getComment() );
205    
206            // ----------------------------------------------------------------------
207            // Mojo annotations in alphabetical order
208            // ----------------------------------------------------------------------
209    
210            // Aggregator flag
211            DocletTag aggregator = findInClassHierarchy( javaClass, JavaMojoAnnotation.AGGREGATOR );
212            if ( aggregator != null )
213            {
214                mojoDescriptor.setAggregator( true );
215            }
216    
217            // Configurator hint
218            DocletTag configurator = findInClassHierarchy( javaClass, JavaMojoAnnotation.CONFIGURATOR );
219            if ( configurator != null )
220            {
221                mojoDescriptor.setComponentConfigurator( configurator.getValue() );
222            }
223    
224            // Additional phase to execute first
225            DocletTag execute = findInClassHierarchy( javaClass, JavaMojoAnnotation.EXECUTE );
226            if ( execute != null )
227            {
228                String executePhase = execute.getNamedParameter( JavaMojoAnnotation.EXECUTE_PHASE );
229                String executeGoal = execute.getNamedParameter( JavaMojoAnnotation.EXECUTE_GOAL );
230    
231                if ( executePhase == null && executeGoal == null )
232                {
233                    throw new InvalidPluginDescriptorException( javaClass.getFullyQualifiedName()
234                        + ": @execute tag requires either a 'phase' or 'goal' parameter" );
235                }
236                else if ( executePhase != null && executeGoal != null )
237                {
238                    throw new InvalidPluginDescriptorException( javaClass.getFullyQualifiedName()
239                        + ": @execute tag can have only one of a 'phase' or 'goal' parameter" );
240                }
241                mojoDescriptor.setExecutePhase( executePhase );
242                mojoDescriptor.setExecuteGoal( executeGoal );
243    
244                String lifecycle = execute.getNamedParameter( JavaMojoAnnotation.EXECUTE_LIFECYCLE );
245                if ( lifecycle != null )
246                {
247                    mojoDescriptor.setExecuteLifecycle( lifecycle );
248                    if ( mojoDescriptor.getExecuteGoal() != null )
249                    {
250                        throw new InvalidPluginDescriptorException( javaClass.getFullyQualifiedName()
251                            + ": @execute lifecycle requires a phase instead of a goal" );
252                    }
253                }
254            }
255    
256            // Goal name
257            DocletTag goal = findInClassHierarchy( javaClass, JavaMojoAnnotation.GOAL );
258            if ( goal != null )
259            {
260                mojoDescriptor.setGoal( goal.getValue() );
261            }
262    
263            // inheritByDefault flag
264            boolean value =
265                getBooleanTagValue( javaClass, JavaMojoAnnotation.INHERIT_BY_DEFAULT,
266                                    mojoDescriptor.isInheritedByDefault() );
267            mojoDescriptor.setInheritedByDefault( value );
268    
269            // instantiationStrategy
270            DocletTag tag = findInClassHierarchy( javaClass, JavaMojoAnnotation.INSTANTIATION_STRATEGY );
271            if ( tag != null )
272            {
273                mojoDescriptor.setInstantiationStrategy( tag.getValue() );
274            }
275    
276            // executionStrategy (and deprecated @attainAlways)
277            tag = findInClassHierarchy( javaClass, JavaMojoAnnotation.MULTI_EXECUTION_STRATEGY );
278            if ( tag != null )
279            {
280                getLogger().warn( "@" + JavaMojoAnnotation.MULTI_EXECUTION_STRATEGY + " in "
281                                      + javaClass.getFullyQualifiedName() + " is deprecated: please use '@"
282                                      + JavaMojoAnnotation.EXECUTION_STATEGY + " always' instead." );
283                mojoDescriptor.setExecutionStrategy( MojoDescriptor.MULTI_PASS_EXEC_STRATEGY );
284            }
285            else
286            {
287                mojoDescriptor.setExecutionStrategy( MojoDescriptor.SINGLE_PASS_EXEC_STRATEGY );
288            }
289            tag = findInClassHierarchy( javaClass, JavaMojoAnnotation.EXECUTION_STATEGY );
290            if ( tag != null )
291            {
292                mojoDescriptor.setExecutionStrategy( tag.getValue() );
293            }
294    
295            // Phase name
296            DocletTag phase = findInClassHierarchy( javaClass, JavaMojoAnnotation.PHASE );
297            if ( phase != null )
298            {
299                mojoDescriptor.setPhase( phase.getValue() );
300            }
301    
302            // Dependency resolution flag
303            DocletTag requiresDependencyResolution =
304                findInClassHierarchy( javaClass, JavaMojoAnnotation.REQUIRES_DEPENDENCY_RESOLUTION );
305            if ( requiresDependencyResolution != null )
306            {
307                String v = requiresDependencyResolution.getValue();
308    
309                if ( StringUtils.isEmpty( v ) )
310                {
311                    v = "runtime";
312                }
313    
314                mojoDescriptor.setDependencyResolutionRequired( v );
315            }
316    
317            // Dependency collection flag
318            DocletTag requiresDependencyCollection =
319                findInClassHierarchy( javaClass, JavaMojoAnnotation.REQUIRES_DEPENDENCY_COLLECTION );
320            if ( requiresDependencyCollection != null )
321            {
322                String v = requiresDependencyCollection.getValue();
323    
324                if ( StringUtils.isEmpty( v ) )
325                {
326                    v = "runtime";
327                }
328    
329                mojoDescriptor.setDependencyCollectionRequired( v );
330            }
331    
332            // requiresDirectInvocation flag
333            value =
334                getBooleanTagValue( javaClass, JavaMojoAnnotation.REQUIRES_DIRECT_INVOCATION,
335                                    mojoDescriptor.isDirectInvocationOnly() );
336            mojoDescriptor.setDirectInvocationOnly( value );
337    
338            // Online flag
339            value =
340                getBooleanTagValue( javaClass, JavaMojoAnnotation.REQUIRES_ONLINE, mojoDescriptor.isOnlineRequired() );
341            mojoDescriptor.setOnlineRequired( value );
342    
343            // Project flag
344            value =
345                getBooleanTagValue( javaClass, JavaMojoAnnotation.REQUIRES_PROJECT, mojoDescriptor.isProjectRequired() );
346            mojoDescriptor.setProjectRequired( value );
347    
348            // requiresReports flag
349            value =
350                getBooleanTagValue( javaClass, JavaMojoAnnotation.REQUIRES_REPORTS, mojoDescriptor.isRequiresReports() );
351            mojoDescriptor.setRequiresReports( value );
352    
353            // ----------------------------------------------------------------------
354            // Javadoc annotations in alphabetical order
355            // ----------------------------------------------------------------------
356    
357            // Deprecation hint
358            DocletTag deprecated = javaClass.getTagByName( JavaMojoAnnotation.DEPRECATED );
359            if ( deprecated != null )
360            {
361                mojoDescriptor.setDeprecated( deprecated.getValue() );
362            }
363    
364            // What version it was introduced in
365            DocletTag since = findInClassHierarchy( javaClass, JavaMojoAnnotation.SINCE );
366            if ( since != null )
367            {
368                mojoDescriptor.setSince( since.getValue() );
369            }
370    
371            // Thread-safe mojo 
372    
373            value = getBooleanTagValue( javaClass, JavaMojoAnnotation.THREAD_SAFE, true, mojoDescriptor.isThreadSafe() );
374            mojoDescriptor.setThreadSafe( value );
375    
376            extractParameters( mojoDescriptor, javaClass );
377    
378            return mojoDescriptor;
379        }
380    
381        /**
382         * @param javaClass not null
383         * @param tagName not null
384         * @param defaultValue the wanted default value
385         * @return the boolean value of the given tagName
386         * @see #findInClassHierarchy(JavaClass, String)
387         */
388        private static boolean getBooleanTagValue( JavaClass javaClass, String tagName, boolean defaultValue )
389        {
390            DocletTag tag = findInClassHierarchy( javaClass, tagName );
391    
392            if ( tag != null )
393            {
394                String value = tag.getValue();
395    
396                if ( StringUtils.isNotEmpty( value ) )
397                {
398                    defaultValue = Boolean.valueOf( value ).booleanValue();
399                }
400            }
401            return defaultValue;
402        }
403    
404        /**
405         * @param javaClass     not null
406         * @param tagName       not null
407         * @param defaultForTag The wanted default value when only the tagname is present
408         * @param defaultValue  the wanted default value when the tag is not specified
409         * @return the boolean value of the given tagName
410         * @see #findInClassHierarchy(JavaClass, String)
411         */
412        private static boolean getBooleanTagValue( JavaClass javaClass, String tagName, boolean defaultForTag,
413                                                   boolean defaultValue )
414        {
415            DocletTag tag = findInClassHierarchy( javaClass, tagName );
416    
417            if ( tag != null )
418            {
419                String value = tag.getValue();
420    
421                if ( StringUtils.isNotEmpty( value ) )
422                {
423                    return Boolean.valueOf( value ).booleanValue();
424                }
425                else
426                {
427                    return defaultForTag;
428                }
429            }
430            return defaultValue;
431        }
432    
433        /**
434         * @param javaClass not null
435         * @param tagName not null
436         * @return docletTag instance
437         */
438        private static DocletTag findInClassHierarchy( JavaClass javaClass, String tagName )
439        {
440            DocletTag tag = javaClass.getTagByName( tagName );
441    
442            if ( tag == null )
443            {
444                JavaClass superClass = javaClass.getSuperJavaClass();
445    
446                if ( superClass != null )
447                {
448                    tag = findInClassHierarchy( superClass, tagName );
449                }
450            }
451    
452            return tag;
453        }
454    
455        /**
456         * @param mojoDescriptor not null
457         * @param javaClass not null
458         * @throws InvalidPluginDescriptorException if any
459         */
460        private void extractParameters( MojoDescriptor mojoDescriptor, JavaClass javaClass )
461            throws InvalidPluginDescriptorException
462        {
463            // ---------------------------------------------------------------------------------
464            // We're resolving class-level, ancestor-class-field, local-class-field order here.
465            // ---------------------------------------------------------------------------------
466    
467            Map<String, JavaField> rawParams = extractFieldParameterTags( javaClass );
468    
469            for ( Map.Entry<String, JavaField> entry : rawParams.entrySet() )
470            {
471                JavaField field = entry.getValue();
472    
473                Type type = field.getType();
474    
475                Parameter pd = new Parameter();
476    
477                pd.setName( entry.getKey() );
478    
479                if ( !type.isArray() )
480                {
481                    pd.setType( type.getValue() );
482                }
483                else
484                {
485                    StringBuilder value = new StringBuilder( type.getValue() );
486    
487                    int remaining = type.getDimensions();
488    
489                    while ( remaining-- > 0 )
490                    {
491                        value.append( "[]" );
492                    }
493    
494                    pd.setType( value.toString() );
495                }
496    
497                pd.setDescription( field.getComment() );
498    
499                DocletTag deprecationTag = field.getTagByName( JavaMojoAnnotation.DEPRECATED );
500    
501                if ( deprecationTag != null )
502                {
503                    pd.setDeprecated( deprecationTag.getValue() );
504                }
505    
506                DocletTag sinceTag = field.getTagByName( JavaMojoAnnotation.SINCE );
507                if ( sinceTag != null )
508                {
509                    pd.setSince( sinceTag.getValue() );
510                }
511    
512                DocletTag componentTag = field.getTagByName( JavaMojoAnnotation.COMPONENT );
513    
514                if ( componentTag != null )
515                {
516                    // Component tag
517                    String role = componentTag.getNamedParameter( JavaMojoAnnotation.COMPONENT_ROLE );
518    
519                    if ( role == null )
520                    {
521                        role = field.getType().toString();
522                    }
523    
524                    String roleHint = componentTag.getNamedParameter( JavaMojoAnnotation.COMPONENT_ROLEHINT );
525    
526                    if ( roleHint == null )
527                    {
528                        // support alternate syntax for better compatibility with the Plexus CDC.
529                        roleHint = componentTag.getNamedParameter( "role-hint" );
530                    }
531    
532                    // recognize Maven-injected objects as components annotations instead of parameters
533                    String expression = PluginUtils.MAVEN_COMPONENTS.get( role );
534    
535                    if ( expression == null )
536                    {
537                        // normal component
538                        pd.setRequirement( new Requirement( role, roleHint ) );
539                    }
540                    else
541                    {
542                        // not a component but a Maven object to be transformed into an expression/property
543                        pd.setDefaultValue( expression );
544                        pd.setType( role );
545                        pd.setRequired( true );
546                    }
547    
548                    pd.setEditable( false );
549                    /* TODO: or better like this? Need @component fields be editable for the user?
550                    pd.setEditable( field.getTagByName( READONLY ) == null );
551                    */
552                }
553                else
554                {
555                    // Parameter tag
556                    DocletTag parameter = field.getTagByName( JavaMojoAnnotation.PARAMETER );
557    
558                    pd.setRequired( field.getTagByName( JavaMojoAnnotation.REQUIRED ) != null );
559    
560                    pd.setEditable( field.getTagByName( JavaMojoAnnotation.READONLY ) == null );
561    
562                    String alias = parameter.getNamedParameter( JavaMojoAnnotation.PARAMETER_ALIAS );
563    
564                    if ( !StringUtils.isEmpty( alias ) )
565                    {
566                        pd.setAlias( alias );
567                    }
568    
569                    String expression = parameter.getNamedParameter( JavaMojoAnnotation.PARAMETER_EXPRESSION );
570                    String property = parameter.getNamedParameter( JavaMojoAnnotation.PARAMETER_PROPERTY );
571    
572                    if ( StringUtils.isNotEmpty( expression ) && StringUtils.isNotEmpty( property ) )
573                    {
574                        getLogger().error( javaClass.getFullyQualifiedName() + "#" + field.getName() + ":" );
575                        getLogger().error( "  Cannot use both:" );
576                        getLogger().error( "    @parameter expression=\"${property}\"" );
577                        getLogger().error( "  and" );
578                        getLogger().error( "    @parameter property=\"property\"" );
579                        getLogger().error( "  Second syntax is preferred." );
580                        throw new InvalidParameterException( javaClass.getFullyQualifiedName() + "#" + field.getName()
581                            + ": cannot" + " use both @parameter expression and property", null );
582                    }
583    
584                    if ( StringUtils.isNotEmpty( expression ) )
585                    {
586                        getLogger().warn( javaClass.getFullyQualifiedName() + "#" + field.getName() + ":" );
587                        getLogger().warn( "  The syntax" );
588                        getLogger().warn( "    @parameter expression=\"${property}\"" );
589                        getLogger().warn( "  is deprecated, please use" );
590                        getLogger().warn( "    @parameter property=\"property\"" );
591                        getLogger().warn( "  instead." );
592    
593                    }
594                    else if ( StringUtils.isNotEmpty( property ) )
595                    {
596                        expression = "${" + property + "}";
597                    }
598    
599                    pd.setExpression( expression );
600    
601                    if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
602                    {
603                        getLogger().warn( javaClass.getFullyQualifiedName() + "#" + field.getName() + ":" );
604                        getLogger().warn( "  The syntax" );
605                        getLogger().warn( "    @parameter expression=\"${component.<role>#<roleHint>}\"" );
606                        getLogger().warn( "  is deprecated, please use" );
607                        getLogger().warn( "    @component role=\"<role>\" roleHint=\"<roleHint>\"" );
608                        getLogger().warn( "  instead." );
609                    }
610    
611                    if ( "${reports}".equals( pd.getExpression() ) )
612                    {
613                        mojoDescriptor.setRequiresReports( true );
614                    }
615    
616                    pd.setDefaultValue( parameter.getNamedParameter( JavaMojoAnnotation.PARAMETER_DEFAULT_VALUE ) );
617    
618                    pd.setImplementation( parameter.getNamedParameter( JavaMojoAnnotation.PARAMETER_IMPLEMENTATION ) );
619                }
620    
621                mojoDescriptor.addParameter( pd );
622            }
623        }
624    
625        /**
626         * extract fields that are either parameters or components.
627         * 
628         * @param javaClass not null
629         * @return map with Mojo parameters names as keys
630         */
631        private Map<String, JavaField> extractFieldParameterTags( JavaClass javaClass )
632        {
633            Map<String, JavaField> rawParams;
634    
635            // we have to add the parent fields first, so that they will be overwritten by the local fields if
636            // that actually happens...
637            JavaClass superClass = javaClass.getSuperJavaClass();
638    
639            if ( superClass != null )
640            {
641                rawParams = extractFieldParameterTags( superClass );
642            }
643            else
644            {
645                rawParams = new TreeMap<String, JavaField>();
646            }
647    
648            JavaField[] classFields = javaClass.getFields();
649    
650            if ( classFields != null )
651            {
652                for ( JavaField field : classFields )
653                {
654                    if ( field.getTagByName( JavaMojoAnnotation.PARAMETER ) != null
655                        || field.getTagByName( JavaMojoAnnotation.COMPONENT ) != null )
656                    {
657                        rawParams.put( field.getName(), field );
658                    }
659                }
660            }
661            return rawParams;
662        }
663    
664        /** {@inheritDoc} */
665        public List<MojoDescriptor> execute( MavenProject project, PluginDescriptor pluginDescriptor )
666            throws ExtractionException, InvalidPluginDescriptorException
667        {
668            return execute( new DefaultPluginToolsRequest( project, pluginDescriptor ) );
669        }
670        
671        /** {@inheritDoc} */
672        public List<MojoDescriptor> execute( PluginToolsRequest request )
673            throws ExtractionException, InvalidPluginDescriptorException
674        {
675            JavaClass[] javaClasses = discoverClasses( request );
676    
677            List<MojoDescriptor> descriptors = new ArrayList<MojoDescriptor>();
678    
679            for ( JavaClass javaClass : javaClasses )
680            {
681                DocletTag tag = javaClass.getTagByName( GOAL );
682    
683                if ( tag != null )
684                {
685                    MojoDescriptor mojoDescriptor = createMojoDescriptor( javaClass );
686                    mojoDescriptor.setPluginDescriptor( request.getPluginDescriptor() );
687    
688                    // Validate the descriptor as best we can before allowing it to be processed.
689                    validate( mojoDescriptor );
690    
691                    descriptors.add( mojoDescriptor );
692                }
693            }
694    
695            return descriptors;
696        }
697    
698        /**
699         * @param request The plugin request.
700         * @return an array of java class
701         */
702        @SuppressWarnings( "unchecked" )
703        protected JavaClass[] discoverClasses( final PluginToolsRequest request )
704        {
705            JavaDocBuilder builder = new JavaDocBuilder();
706            builder.setEncoding( request.getEncoding() );
707            
708            MavenProject project = request.getProject();
709    
710            for ( String source : (List<String>) project.getCompileSourceRoots() )
711            {
712                builder.addSourceTree( new File( source ) );
713            }
714    
715            // TODO be more dynamic
716            File generatedPlugin = new File( project.getBasedir(), "target/generated-sources/plugin" );
717            if ( !project.getCompileSourceRoots().contains( generatedPlugin.getAbsolutePath() ) )
718            {
719                builder.addSourceTree( generatedPlugin );
720            }
721    
722            return builder.getClasses();
723        }
724    
725        /**
726         * @param mojoDescriptor not null
727         * @throws InvalidParameterException if any
728         */
729        protected void validate( MojoDescriptor mojoDescriptor )
730            throws InvalidParameterException
731        {
732            @SuppressWarnings( "unchecked" )
733            List<Parameter> parameters = mojoDescriptor.getParameters();
734    
735            if ( parameters != null )
736            {
737                for ( int j = 0; j < parameters.size(); j++ )
738                {
739                    validateParameter( parameters.get( j ), j );
740                }
741            }
742        }
743    }