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