View Javadoc
1   package org.apache.maven.plugins.help;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.net.URL;
27  import java.net.URLClassLoader;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.StringTokenizer;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import org.apache.maven.artifact.Artifact;
36  import org.apache.maven.lifecycle.DefaultLifecycles;
37  import org.apache.maven.lifecycle.Lifecycle;
38  import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
39  import org.apache.maven.lifecycle.mapping.LifecycleMapping;
40  import org.apache.maven.model.Plugin;
41  import org.apache.maven.model.building.ModelBuildingRequest;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.apache.maven.plugin.descriptor.MojoDescriptor;
45  import org.apache.maven.plugin.descriptor.Parameter;
46  import org.apache.maven.plugin.descriptor.PluginDescriptor;
47  import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
48  import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
49  import org.apache.maven.plugin.version.PluginVersionResolutionException;
50  import org.apache.maven.plugin.version.PluginVersionResolver;
51  import org.apache.maven.plugin.version.PluginVersionResult;
52  import org.apache.maven.plugins.annotations.Component;
53  import org.apache.maven.plugins.annotations.Mojo;
54  import org.apache.maven.project.DefaultProjectBuildingRequest;
55  import org.apache.maven.project.MavenProject;
56  import org.apache.maven.project.ProjectBuildingRequest;
57  import org.apache.maven.reporting.MavenReport;
58  import org.apache.maven.reporting.exec.MavenPluginManagerHelper;
59  import org.apache.maven.shared.transfer.artifact.ArtifactCoordinate;
60  import org.apache.maven.tools.plugin.generator.GeneratorUtils;
61  import org.apache.maven.tools.plugin.util.PluginUtils;
62  import org.codehaus.plexus.util.StringUtils;
63  
64  /**
65   * Displays a list of the attributes for a Maven Plugin and/or goals (aka Mojo - Maven plain Old Java Object).
66   *
67   * @see <a href="http://maven.apache.org/general.html#What_is_a_Mojo">What is a Mojo?</a>
68   * @since 2.0
69   */
70  @Mojo( name = "describe", requiresProject = false, aggregator = true )
71  public class DescribeMojo
72      extends AbstractHelpMojo
73  {
74      /**
75       * The default indent size when writing description's Mojo.
76       */
77      private static final int INDENT_SIZE = 2;
78  
79      /**
80       * For unknown values
81       */
82      private static final String UNKNOWN = "Unknown";
83  
84      /**
85       * For not defined values
86       */
87      private static final String NOT_DEFINED = "Not defined";
88  
89      /**
90       * For deprecated values
91       */
92      private static final String NO_REASON = "No reason given";
93  
94      private static final Pattern EXPRESSION = Pattern.compile( "^\\$\\{([^}]+)\\}$" );
95  
96      // ----------------------------------------------------------------------
97      // Mojo components
98      // ----------------------------------------------------------------------
99  
100     /**
101      * Component used to get a plugin descriptor from a given plugin.
102      */
103     @Component
104     private MavenPluginManagerHelper pluginManager;
105 
106     /**
107      * Component used to get a plugin by its prefix and get mojo descriptors.
108      */
109     @Component
110     private MojoDescriptorCreator mojoDescriptorCreator;
111 
112     /**
113      * Component used to resolve the version for a plugin.
114      */
115     @Component
116     private PluginVersionResolver pluginVersionResolver;
117 
118     /**
119      * The Maven default built-in lifecycles.
120      */
121     @Component
122     private DefaultLifecycles defaultLifecycles;
123 
124     /**
125      * A map from each packaging to its lifecycle mapping.
126      */
127     @Component
128     private Map<String, LifecycleMapping> lifecycleMappings;
129 
130     // ----------------------------------------------------------------------
131     // Mojo parameters
132     // ----------------------------------------------------------------------
133 
134     /**
135      * The current project, if there is one. This is listed as optional, since
136      * the help plugin should be able to function on its own. If this
137      * parameter is empty at execution time, this Mojo will instead use the
138      * super-project.
139      */
140     @org.apache.maven.plugins.annotations.Parameter( defaultValue = "${project}", readonly = true, required = true )
141     private MavenProject project;
142 
143     /**
144      * The Maven Plugin to describe. This must be specified in one of three ways:
145      * <br/>
146      * <ol>
147      * <li>plugin-prefix, i.e. 'help'</li>
148      * <li>groupId:artifactId, i.e. 'org.apache.maven.plugins:maven-help-plugin'</li>
149      * <li>groupId:artifactId:version, i.e. 'org.apache.maven.plugins:maven-help-plugin:2.0'</li>
150      * </ol>
151      */
152     @org.apache.maven.plugins.annotations.Parameter( property = "plugin", alias = "prefix" )
153     private String plugin;
154 
155     /**
156      * The Maven Plugin <code>groupId</code> to describe.
157      * <br/>
158      * <b>Note</b>: Should be used with <code>artifactId</code> parameter.
159      */
160     @org.apache.maven.plugins.annotations.Parameter( property = "groupId" )
161     private String groupId;
162 
163     /**
164      * The Maven Plugin <code>artifactId</code> to describe.
165      * <br/>
166      * <b>Note</b>: Should be used with <code>groupId</code> parameter.
167      */
168     @org.apache.maven.plugins.annotations.Parameter( property = "artifactId" )
169     private String artifactId;
170 
171     /**
172      * The Maven Plugin <code>version</code> to describe.
173      * <br/>
174      * <b>Note</b>: Should be used with <code>groupId/artifactId</code> parameters.
175      */
176     @org.apache.maven.plugins.annotations.Parameter( property = "version" )
177     private String version;
178 
179     /**
180      * The goal name of a Mojo to describe within the specified Maven Plugin.
181      * If this parameter is specified, only the corresponding goal (Mojo) will be described,
182      * rather than the whole Plugin.
183      *
184      * @since 2.1
185      */
186     @org.apache.maven.plugins.annotations.Parameter( property = "goal" )
187     private String goal;
188 
189     /**
190      * This flag specifies that a detailed (verbose) list of goal (Mojo) information should be given.
191      *
192      * @since 2.1
193      */
194     @org.apache.maven.plugins.annotations.Parameter( property = "detail", defaultValue = "false" )
195     private boolean detail;
196 
197     /**
198      * This flag specifies that a minimal list of goal (Mojo) information should be given.
199      *
200      * @since 2.1
201      */
202     @org.apache.maven.plugins.annotations.Parameter( property = "minimal", defaultValue = "false" )
203     private boolean minimal;
204 
205     /**
206      * A Maven command like a single goal or a single phase following the Maven command line:
207      * <br/>
208      * <code>mvn [options] [&lt;goal(s)&gt;] [&lt;phase(s)&gt;]</code>
209      *
210      * @since 2.1
211      */
212     @org.apache.maven.plugins.annotations.Parameter( property = "cmd" )
213     private String cmd;
214 
215     // ----------------------------------------------------------------------
216     // Public methods
217     // ----------------------------------------------------------------------
218 
219     /**
220      * {@inheritDoc}
221      */
222     public void execute()
223         throws MojoExecutionException, MojoFailureException
224     {
225         StringBuilder descriptionBuffer = new StringBuilder();
226 
227         boolean describePlugin = true;
228         if ( StringUtils.isNotEmpty( cmd ) )
229         {
230             describePlugin = describeCommand( descriptionBuffer );
231         }
232 
233         if ( describePlugin )
234         {
235             PluginInfo pi = parsePluginLookupInfo();
236             PluginDescriptor descriptor = lookupPluginDescriptor( pi );
237             if ( StringUtils.isNotEmpty( goal ) )
238             {
239                 MojoDescriptor mojo = descriptor.getMojo( goal );
240                 if ( mojo == null )
241                 {
242                     throw new MojoFailureException( "The goal '" + goal + "' does not exist in the plugin '"
243                         + pi.getPrefix() + "'" );
244                 }
245                 describeMojo( mojo, descriptionBuffer );
246             }
247             else
248             {
249                 describePlugin( descriptor, descriptionBuffer );
250             }
251         }
252 
253         writeDescription( descriptionBuffer );
254     }
255 
256     // ----------------------------------------------------------------------
257     // Private methods
258     // ----------------------------------------------------------------------
259 
260     /**
261      * Method to write the Mojo description into the output file
262      *
263      * @param descriptionBuffer contains the description to be written to the file
264      * @throws MojoExecutionException if any
265      */
266     private void writeDescription( StringBuilder descriptionBuffer )
267         throws MojoExecutionException
268     {
269         if ( output != null )
270         {
271             try
272             {
273                 writeFile( output, descriptionBuffer );
274             }
275             catch ( IOException e )
276             {
277                 throw new MojoExecutionException( "Cannot write plugin/goal description to output: " + output, e );
278             }
279 
280             getLog().info( "Wrote descriptions to: " + output );
281         }
282         else
283         {
284             getLog().info( descriptionBuffer.toString() );
285         }
286     }
287 
288     /**
289      * Method for retrieving the description of the plugin
290      *
291      * @param pi holds information of the plugin whose description is to be retrieved
292      * @return a PluginDescriptor where the plugin description is to be retrieved
293      * @throws MojoExecutionException if the plugin could not be verify
294      * @throws MojoFailureException   if groupId or artifactId is empty
295      */
296     private PluginDescriptor lookupPluginDescriptor( PluginInfo pi )
297         throws MojoExecutionException, MojoFailureException
298     {
299         Plugin forLookup = null;
300         if ( StringUtils.isNotEmpty( pi.getPrefix() ) )
301         {
302             try
303             {
304                 forLookup = mojoDescriptorCreator.findPluginForPrefix( pi.getPrefix(), session );
305             }
306             catch ( NoPluginFoundForPrefixException e )
307             {
308                 throw new MojoExecutionException( "Unable to find the plugin with prefix: " + pi.getPrefix(), e );
309             }
310         }
311         else if ( StringUtils.isNotEmpty( pi.getGroupId() ) && StringUtils.isNotEmpty( pi.getArtifactId() ) )
312         {
313             forLookup = new Plugin();
314             forLookup.setGroupId( pi.getGroupId() );
315             forLookup.setArtifactId( pi.getArtifactId() );
316         }
317         if ( forLookup == null )
318         {
319             String msg =
320                 "You must specify either: both 'groupId' and 'artifactId' parameters OR a 'plugin' parameter"
321                   + " OR a 'cmd' parameter. For instance:" + LS
322                   + "  # mvn help:describe -Dcmd=install" + LS
323                   + "or" + LS
324                   + "  # mvn help:describe -Dcmd=help:describe" + LS
325                   + "or" + LS
326                   + "  # mvn help:describe -Dplugin=org.apache.maven.plugins:maven-help-plugin" + LS
327                   + "or" + LS
328                   + "  # mvn help:describe -DgroupId=org.apache.maven.plugins -DartifactId=maven-help-plugin" + LS + LS
329                   + "Try 'mvn help:help -Ddetail=true' for more information.";
330             throw new MojoFailureException( msg );
331         }
332 
333         if ( StringUtils.isNotEmpty( pi.getVersion() ) )
334         {
335             forLookup.setVersion( pi.getVersion() );
336         }
337         else
338         {
339             try
340             {
341                 DefaultPluginVersionRequest versionRequest = new DefaultPluginVersionRequest( forLookup, session );
342                 versionRequest.setPom( project.getModel() );
343                 PluginVersionResult versionResult = pluginVersionResolver.resolve( versionRequest );
344                 forLookup.setVersion( versionResult.getVersion() );
345             }
346             catch ( PluginVersionResolutionException e )
347             {
348                 throw new MojoExecutionException( "Unable to resolve the version of the plugin with prefix: "
349                     + pi.getPrefix(), e );
350             }
351         }
352 
353         try
354         {
355             return pluginManager.getPluginDescriptor( forLookup, session );
356         }
357         catch ( Exception e )
358         {
359             throw new MojoExecutionException( "Error retrieving plugin descriptor for:" + LS + LS + "groupId: '"
360                 + groupId + "'" + LS + "artifactId: '" + artifactId + "'" + LS + "version: '" + version + "'" + LS
361                 + LS, e );
362         }
363     }
364 
365     /**
366      * Method for parsing the plugin parameter
367      *
368      * @return Plugin info containing information about the plugin whose description is to be retrieved
369      * @throws MojoFailureException if <code>plugin<*code> parameter is not conform to
370      *                              <code>groupId:artifactId[:version]</code>
371      */
372     private PluginInfo parsePluginLookupInfo()
373         throws MojoFailureException
374     {
375         PluginInfo pi = new PluginInfo();
376         if ( StringUtils.isNotEmpty( plugin ) )
377         {
378             if ( plugin.indexOf( ':' ) > -1 )
379             {
380                 String[] pluginParts = plugin.split( ":" );
381 
382                 switch ( pluginParts.length )
383                 {
384                     case 1:
385                         pi.setPrefix( pluginParts[0] );
386                         break;
387                     case 2:
388                         pi.setGroupId( pluginParts[0] );
389                         pi.setArtifactId( pluginParts[1] );
390                         break;
391                     case 3:
392                         pi.setGroupId( pluginParts[0] );
393                         pi.setArtifactId( pluginParts[1] );
394                         pi.setVersion( pluginParts[2] );
395                         break;
396                     default:
397                         throw new MojoFailureException( "plugin parameter must be a plugin prefix,"
398                                                             + " or conform to: 'groupId:artifactId[:version]'." );
399                 }
400             }
401             else
402             {
403                 pi.setPrefix( plugin );
404             }
405         }
406         else
407         {
408             pi.setGroupId( groupId );
409             pi.setArtifactId( artifactId );
410             pi.setVersion( version );
411         }
412         return pi;
413     }
414 
415     /**
416      * Method for retrieving the plugin description
417      *
418      * @param pd     contains the plugin description
419      * @param buffer contains the information to be displayed or printed
420      * @throws MojoFailureException   if any reflection exceptions occur.
421      * @throws MojoExecutionException if any
422      */
423     private void describePlugin( PluginDescriptor pd, StringBuilder buffer )
424         throws MojoFailureException, MojoExecutionException
425     {
426         append( buffer, pd.getId(), 0 );
427         buffer.append( LS );
428 
429         String name = pd.getName();
430         if ( name == null )
431         {
432             // Can be null because of MPLUGIN-137 (and descriptors generated with maven-plugin-tools-api <= 2.4.3)
433             ArtifactCoordinate coordinate = toArtifactCoordinate( pd, "jar" );
434             ProjectBuildingRequest pbr = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
435             pbr.setRemoteRepositories( remoteRepositories );
436             pbr.setProject( null );
437             pbr.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
438             try
439             {
440                 Artifact artifact = artifactResolver.resolveArtifact( pbr, coordinate ).getArtifact();
441                 name = projectBuilder.build( artifact, pbr ).getProject().getName();
442             }
443             catch ( Exception e )
444             {
445                 // oh well, we tried our best.
446                 getLog().warn( "Unable to get the name of the plugin " + pd.getId() + ": " + e.getMessage() );
447                 name = pd.getId();
448             }
449         }
450         append( buffer, "Name", name, 0 );
451         appendAsParagraph( buffer, "Description", toDescription( pd.getDescription() ), 0 );
452         append( buffer, "Group Id", pd.getGroupId(), 0 );
453         append( buffer, "Artifact Id", pd.getArtifactId(), 0 );
454         append( buffer, "Version", pd.getVersion(), 0 );
455         append( buffer, "Goal Prefix", pd.getGoalPrefix(), 0 );
456         buffer.append( LS );
457 
458         List<MojoDescriptor> mojos = pd.getMojos();
459 
460         if ( mojos == null )
461         {
462             append( buffer, "This plugin has no goals.", 0 );
463             return;
464         }
465 
466         if ( !minimal )
467         {
468             append( buffer, "This plugin has " + mojos.size() + " goal" + ( mojos.size() > 1 ? "s" : "" ) + ":", 0 );
469             buffer.append( LS );
470 
471             mojos = new ArrayList<MojoDescriptor>( mojos );
472             PluginUtils.sortMojos( mojos );
473 
474             for ( MojoDescriptor md : mojos )
475             {
476                 describeMojoGuts( md, buffer, detail );
477                 buffer.append( LS );
478             }
479         }
480 
481         if ( !detail )
482         {
483             buffer.append( "For more information, run 'mvn help:describe [...] -Ddetail'" );
484             buffer.append( LS );
485         }
486     }
487 
488     /**
489      * Displays information about the Plugin Mojo
490      *
491      * @param md     contains the description of the Plugin Mojo
492      * @param buffer the displayed output
493      * @throws MojoFailureException   if any reflection exceptions occur.
494      * @throws MojoExecutionException if any
495      */
496     private void describeMojo( MojoDescriptor md, StringBuilder buffer )
497         throws MojoFailureException, MojoExecutionException
498     {
499         buffer.append( "Mojo: '" ).append( md.getFullGoalName() ).append( "'" );
500         buffer.append( LS );
501 
502         describeMojoGuts( md, buffer, detail );
503         buffer.append( LS );
504 
505         if ( !detail )
506         {
507             buffer.append( "For more information, run 'mvn help:describe [...] -Ddetail'" );
508             buffer.append( LS );
509         }
510     }
511 
512     /**
513      * Displays detailed information about the Plugin Mojo
514      *
515      * @param md              contains the description of the Plugin Mojo
516      * @param buffer          contains information to be printed or displayed
517      * @param fullDescription specifies whether all the details about the Plugin Mojo is to  be displayed
518      * @throws MojoFailureException   if any reflection exceptions occur.
519      * @throws MojoExecutionException if any
520      */
521     private void describeMojoGuts( MojoDescriptor md, StringBuilder buffer, boolean fullDescription )
522         throws MojoFailureException, MojoExecutionException
523     {
524         append( buffer, md.getFullGoalName(), 0 );
525 
526         // indent 1
527         appendAsParagraph( buffer, "Description", toDescription( md.getDescription() ), 1 );
528 
529         String deprecation = md.getDeprecated();
530         if ( deprecation != null && deprecation.length() <= 0 )
531         {
532             deprecation = NO_REASON;
533         }
534 
535         if ( StringUtils.isNotEmpty( deprecation ) )
536         {
537             append( buffer, "Deprecated. " + deprecation, 1 );
538         }
539 
540         if ( isReportGoal( md ) )
541         {
542             append( buffer, "Note", "This goal should be used as a Maven report.", 1 );
543         }
544 
545         if ( !fullDescription )
546         {
547             return;
548         }
549 
550         append( buffer, "Implementation", md.getImplementation(), 1 );
551         append( buffer, "Language", md.getLanguage(), 1 );
552 
553         String phase = md.getPhase();
554         if ( StringUtils.isNotEmpty( phase ) )
555         {
556             append( buffer, "Bound to phase", phase, 1 );
557         }
558 
559         String eGoal = md.getExecuteGoal();
560         String eLife = md.getExecuteLifecycle();
561         String ePhase = md.getExecutePhase();
562 
563         if ( StringUtils.isNotEmpty( eGoal ) || StringUtils.isNotEmpty( ePhase ) )
564         {
565             append( buffer, "Before this goal executes, it will call:", 1 );
566 
567             if ( StringUtils.isNotEmpty( eGoal ) )
568             {
569                 append( buffer, "Single goal", "'" + eGoal + "'", 2 );
570             }
571 
572             if ( StringUtils.isNotEmpty( ePhase ) )
573             {
574                 String s = "Phase: '" + ePhase + "'";
575 
576                 if ( StringUtils.isNotEmpty( eLife ) )
577                 {
578                     s += " in Lifecycle Overlay: '" + eLife + "'";
579                 }
580 
581                 append( buffer, s, 2 );
582             }
583         }
584 
585         buffer.append( LS );
586 
587         describeMojoParameters( md, buffer );
588     }
589 
590     /**
591      * Displays parameter information of the Plugin Mojo
592      *
593      * @param md     contains the description of the Plugin Mojo
594      * @param buffer contains information to be printed or displayed
595      * @throws MojoFailureException   if any reflection exceptions occur.
596      * @throws MojoExecutionException if any
597      */
598     private void describeMojoParameters( MojoDescriptor md, StringBuilder buffer )
599         throws MojoFailureException, MojoExecutionException
600     {
601         List<Parameter> params = md.getParameters();
602 
603         if ( params == null || params.isEmpty() )
604         {
605             append( buffer, "This mojo doesn't use any parameters.", 1 );
606             return;
607         }
608 
609         params = new ArrayList<Parameter>( params );
610         PluginUtils.sortMojoParameters( params );
611 
612         append( buffer, "Available parameters:", 1 );
613 
614         // indent 2
615         for ( Parameter parameter : params )
616         {
617             if ( !parameter.isEditable() )
618             {
619                 continue;
620             }
621 
622             buffer.append( LS );
623 
624             // DGF wouldn't it be nice if this worked?
625             String defaultVal = parameter.getDefaultValue();
626             if ( defaultVal == null )
627             {
628                 // defaultVal is ALWAYS null, this is a bug in PluginDescriptorBuilder (cf. MNG-4941)
629                 defaultVal =
630                     md.getMojoConfiguration().getChild( parameter.getName() ).getAttribute( "default-value", null );
631             }
632 
633             if ( StringUtils.isNotEmpty( defaultVal ) )
634             {
635                 defaultVal = " (Default: " + defaultVal + ")";
636             }
637             else
638             {
639                 defaultVal = "";
640             }
641             append( buffer, parameter.getName() + defaultVal, 2 );
642 
643             String alias = parameter.getAlias();
644             if ( !StringUtils.isEmpty( alias ) )
645             {
646                 append ( buffer, "Alias", alias, 3 );
647             }
648 
649             if ( parameter.isRequired() )
650             {
651                 append( buffer, "Required", "true", 3 );
652             }
653 
654             String expression = parameter.getExpression();
655             if ( StringUtils.isEmpty( expression ) )
656             {
657                 // expression is ALWAYS null, this is a bug in PluginDescriptorBuilder (cf. MNG-4941).
658                 // Fixed with Maven-3.0.1
659                 expression = md.getMojoConfiguration().getChild( parameter.getName() ).getValue( null );
660             }
661             if ( StringUtils.isNotEmpty( expression ) )
662             {
663                 Matcher matcher = EXPRESSION.matcher( expression );
664                 if ( matcher.matches() )
665                 {
666                     append( buffer, "User property", matcher.group( 1 ), 3 );
667                 }
668                 else
669                 {
670                     append( buffer, "Expression", expression, 3 );
671                 }
672             }
673 
674             append( buffer, toDescription( parameter.getDescription() ), 3 );
675 
676             String deprecation = parameter.getDeprecated();
677             if ( deprecation != null && deprecation.length() <= 0 )
678             {
679                 deprecation = NO_REASON;
680             }
681 
682             if ( StringUtils.isNotEmpty( deprecation ) )
683             {
684                 append( buffer, "Deprecated. " + deprecation, 3 );
685             }
686         }
687     }
688 
689     /**
690      * Describe the <code>cmd</code> parameter
691      *
692      * @param descriptionBuffer not null
693      * @return <code>true</code> if it implies to describe a plugin, <code>false</code> otherwise.
694      * @throws MojoFailureException   if any reflection exceptions occur or missing components.
695      * @throws MojoExecutionException if any
696      */
697     private boolean describeCommand( StringBuilder descriptionBuffer )
698         throws MojoFailureException, MojoExecutionException
699     {
700         if ( cmd.indexOf( ':' ) == -1 )
701         {
702             // phase
703             Lifecycle lifecycle = defaultLifecycles.getPhaseToLifecycleMap().get( cmd );
704             if ( lifecycle == null )
705             {
706                 throw new MojoExecutionException( "The given phase '" + cmd + "' is an unknown phase." );
707             }
708 
709             Map<String, String> defaultLifecyclePhases =
710                 lifecycleMappings.get( project.getPackaging() ).getLifecycles().get( "default" ).getPhases();
711             List<String> phases = lifecycle.getPhases();
712 
713             if ( lifecycle.getDefaultPhases() == null )
714             {
715                 descriptionBuffer.append( "'" ).append( cmd );
716                 descriptionBuffer.append( "' is a phase corresponding to this plugin:" ).append( LS );
717                 for ( String key : phases )
718                 {
719                     if ( !key.equals( cmd ) )
720                     {
721                         continue;
722                     }
723                     if ( defaultLifecyclePhases.get( key ) != null )
724                     {
725                         descriptionBuffer.append( defaultLifecyclePhases.get( key ) );
726                         descriptionBuffer.append( LS );
727                     }
728                 }
729 
730                 descriptionBuffer.append( LS );
731                 descriptionBuffer.append( "It is a part of the lifecycle for the POM packaging '" );
732                 descriptionBuffer.append( project.getPackaging() );
733                 descriptionBuffer.append( "'. This lifecycle includes the following phases:" );
734                 descriptionBuffer.append( LS );
735                 for ( String key : phases )
736                 {
737                     descriptionBuffer.append( "* " ).append( key ).append( ": " );
738                     String value = defaultLifecyclePhases.get( key );
739                     if ( StringUtils.isNotEmpty( value ) )
740                     {
741                         for ( StringTokenizer tok = new StringTokenizer( value, "," ); tok.hasMoreTokens(); )
742                         {
743                             descriptionBuffer.append( tok.nextToken().trim() );
744 
745                             if ( !tok.hasMoreTokens() )
746                             {
747                                 descriptionBuffer.append( LS );
748                             }
749                             else
750                             {
751                                 descriptionBuffer.append( ", " );
752                             }
753                         }
754                     }
755                     else
756                     {
757                         descriptionBuffer.append( NOT_DEFINED ).append( LS );
758                     }
759                 }
760             }
761             else
762             {
763                 descriptionBuffer.append( "'" ).append( cmd );
764                 descriptionBuffer.append( "' is a phase within the '" ).append( lifecycle.getId() );
765                 descriptionBuffer.append( "' lifecycle, which has the following phases: " );
766                 descriptionBuffer.append( LS );
767 
768                 for ( String key : phases )
769                 {
770                     descriptionBuffer.append( "* " ).append( key ).append( ": " );
771                     if ( lifecycle.getDefaultPhases().get( key ) != null )
772                     {
773                         descriptionBuffer.append( lifecycle.getDefaultPhases().get( key ) ).append( LS );
774                     }
775                     else
776                     {
777                         descriptionBuffer.append( NOT_DEFINED ).append( LS );
778                     }
779                 }
780             }
781             return false;
782         }
783 
784         // goals
785         MojoDescriptor mojoDescriptor;
786         try
787         {
788             mojoDescriptor = mojoDescriptorCreator.getMojoDescriptor( cmd, session, project );
789         }
790         catch ( Exception e )
791         {
792             throw new MojoExecutionException( "Unable to get descriptor for " + cmd, e );
793         }
794         descriptionBuffer.append( "'" ).append( cmd ).append( "' is a plugin goal (aka mojo)" ).append( "." );
795         descriptionBuffer.append( LS );
796         plugin = mojoDescriptor.getPluginDescriptor().getId();
797         goal = mojoDescriptor.getGoal();
798 
799         return true;
800     }
801 
802     /**
803      * Invoke the following private method
804      * <code>HelpMojo#toLines(String, int, int, int)</code>
805      *
806      * @param text       The text to split into lines, must not be <code>null</code>.
807      * @param indent     The base indentation level of each line, must not be negative.
808      * @param indentSize The size of each indentation, must not be negative.
809      * @param lineLength The length of the line, must not be negative.
810      * @return The sequence of display lines, never <code>null</code>.
811      * @throws MojoFailureException   if any can not invoke the method
812      * @throws MojoExecutionException if no line was found for <code>text</code>
813      * @see HelpMojo#toLines(String, int, int, int)
814      */
815     private static List<String> toLines( String text, int indent, int indentSize, int lineLength )
816         throws MojoFailureException, MojoExecutionException
817     {
818         try
819         {
820             Method m = HelpMojo.class.getDeclaredMethod( "toLines",
821                                                          new Class[]{ String.class, Integer.TYPE, Integer.TYPE,
822                                                              Integer.TYPE } );
823             m.setAccessible( true );
824             @SuppressWarnings( "unchecked" )
825             List<String> output = (List<String>) m.invoke( HelpMojo.class, text, indent, indentSize, lineLength );
826 
827             if ( output == null )
828             {
829                 throw new MojoExecutionException( "No output was specified." );
830             }
831 
832             return output;
833         }
834         catch ( SecurityException e )
835         {
836             throw new MojoFailureException( "SecurityException: " + e.getMessage() );
837         }
838         catch ( IllegalArgumentException e )
839         {
840             throw new MojoFailureException( "IllegalArgumentException: " + e.getMessage() );
841         }
842         catch ( NoSuchMethodException e )
843         {
844             throw new MojoFailureException( "NoSuchMethodException: " + e.getMessage() );
845         }
846         catch ( IllegalAccessException e )
847         {
848             throw new MojoFailureException( "IllegalAccessException: " + e.getMessage() );
849         }
850         catch ( InvocationTargetException e )
851         {
852             Throwable cause = e.getCause();
853 
854             if ( cause instanceof NegativeArraySizeException )
855             {
856                 throw new MojoFailureException( "NegativeArraySizeException: " + cause.getMessage() );
857             }
858 
859             throw new MojoFailureException( "InvocationTargetException: " + e.getMessage() );
860         }
861     }
862 
863     /**
864      * Append a description to the buffer by respecting the indentSize and lineLength parameters.
865      * <b>Note</b>: The last character is always a new line.
866      *
867      * @param sb          The buffer to append the description, not <code>null</code>.
868      * @param description The description, not <code>null</code>.
869      * @param indent      The base indentation level of each line, must not be negative.
870      * @throws MojoFailureException   if any reflection exceptions occur.
871      * @throws MojoExecutionException if any
872      * @see #toLines(String, int, int, int)
873      */
874     private static void append( StringBuilder sb, String description, int indent )
875         throws MojoFailureException, MojoExecutionException
876     {
877         if ( StringUtils.isEmpty( description ) )
878         {
879             sb.append( UNKNOWN ).append( LS );
880             return;
881         }
882 
883         for ( String line : toLines( description, indent, INDENT_SIZE, LINE_LENGTH ) )
884         {
885             sb.append( line ).append( LS );
886         }
887     }
888 
889     /**
890      * Append a description to the buffer by respecting the indentSize and lineLength parameters.
891      * <b>Note</b>: The last character is always a new line.
892      *
893      * @param sb     The buffer to append the description, not <code>null</code>.
894      * @param key    The key, not <code>null</code>.
895      * @param value  The value associated to the key, could be <code>null</code>.
896      * @param indent The base indentation level of each line, must not be negative.
897      * @throws MojoFailureException   if any reflection exceptions occur.
898      * @throws MojoExecutionException if any
899      * @see #toLines(String, int, int, int)
900      */
901     private static void append( StringBuilder sb, String key, String value, int indent )
902         throws MojoFailureException, MojoExecutionException
903     {
904         if ( StringUtils.isEmpty( key ) )
905         {
906             throw new IllegalArgumentException( "Key is required!" );
907         }
908 
909         if ( StringUtils.isEmpty( value ) )
910         {
911             value = UNKNOWN;
912         }
913 
914         String description = key + ": " + value;
915         for ( String line : toLines( description, indent, INDENT_SIZE, LINE_LENGTH ) )
916         {
917             sb.append( line ).append( LS );
918         }
919     }
920 
921     /**
922      * Append a description to the buffer by respecting the indentSize and lineLength parameters for the first line,
923      * and append the next lines with <code>indent + 1</code> like a paragraph.
924      * <b>Note</b>: The last character is always a new line.
925      *
926      * @param sb     The buffer to append the description, not <code>null</code>.
927      * @param key    The key, not <code>null</code>.
928      * @param value  The value, could be <code>null</code>.
929      * @param indent The base indentation level of each line, must not be negative.
930      * @throws MojoFailureException   if any reflection exceptions occur.
931      * @throws MojoExecutionException if any
932      * @see #toLines(String, int, int, int)
933      */
934     private static void appendAsParagraph( StringBuilder sb, String key, String value, int indent )
935         throws MojoFailureException, MojoExecutionException
936     {
937         if ( StringUtils.isEmpty( value ) )
938         {
939             value = UNKNOWN;
940         }
941 
942         String description;
943         if ( key == null )
944         {
945             description = value;
946         }
947         else
948         {
949             description = key + ": " + value;
950         }
951 
952         List<String> l1 = toLines( description, indent, INDENT_SIZE, LINE_LENGTH - INDENT_SIZE );
953         List<String> l2 = toLines( description, indent + 1, INDENT_SIZE, LINE_LENGTH );
954         l2.set( 0, l1.get( 0 ) );
955         for ( String line : l2 )
956         {
957             sb.append( line ).append( LS );
958         }
959     }
960 
961     /**
962      * Determines if this Mojo should be used as a report or not. This resolves the plugin project along with all of its
963      * transitive dependencies to determine if the Java class of this goal implements <code>MavenReport</code>.
964      *
965      * @param md Mojo descriptor
966      * @return Whether or not this goal should be used as a report.
967      */
968     private boolean isReportGoal( MojoDescriptor md )
969     {
970         PluginDescriptor pd = md.getPluginDescriptor();
971         List<URL> urls = new ArrayList<URL>();
972         ProjectBuildingRequest pbr = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
973         pbr.setRemoteRepositories( remoteRepositories );
974         pbr.setResolveDependencies( true );
975         pbr.setProject( null );
976         pbr.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
977         try
978         {
979             Artifact jar = artifactResolver.resolveArtifact( pbr, toArtifactCoordinate( pd, "jar" ) ).getArtifact();
980             Artifact pom = artifactResolver.resolveArtifact( pbr, toArtifactCoordinate( pd, "pom" ) ).getArtifact();
981             MavenProject project = projectBuilder.build( pom.getFile(), pbr ).getProject();
982             urls.add( jar.getFile().toURI().toURL() );
983             for ( Object artifact : project.getCompileClasspathElements() )
984             {
985                 urls.add( new File( (String) artifact ).toURI().toURL() );
986             }
987             ClassLoader classLoader =
988                 new URLClassLoader( urls.toArray( new URL[urls.size()] ), getClass().getClassLoader() );
989             return MavenReport.class.isAssignableFrom( Class.forName( md.getImplementation(), false, classLoader ) );
990         }
991         catch ( Exception e )
992         {
993             getLog().warn( "Couldn't identify if this goal is a report goal: " + e.getMessage() );
994             return false;
995         }
996     }
997 
998     /**
999      * Transforms the given plugin descriptor into an artifact coordinate. It is formed by its GAV information, along
1000      * with the given type.
1001      *
1002      * @param pd Plugin descriptor.
1003      * @param type Extension for the coordinate.
1004      * @return Coordinate of an artifact having the same GAV as the given plugin descriptor, with the given type.
1005      */
1006     private ArtifactCoordinate toArtifactCoordinate( PluginDescriptor pd, String type )
1007     {
1008         return getArtifactCoordinate( pd.getGroupId(), pd.getArtifactId(), pd.getVersion(), type );
1009     }
1010 
1011     /**
1012      * Gets the effective string to use for the plugin/mojo/parameter description.
1013      *
1014      * @param description The description of the element, may be <code>null</code>.
1015      * @return The effective description string, never <code>null</code>.
1016      */
1017     private static String toDescription( String description )
1018     {
1019         if ( StringUtils.isNotEmpty( description ) )
1020         {
1021             return GeneratorUtils.toText( description );
1022         }
1023 
1024         return "(no description available)";
1025     }
1026 
1027     /**
1028      * Class to wrap Plugin information.
1029      */
1030     static class PluginInfo
1031     {
1032         private String prefix;
1033 
1034         private String groupId;
1035 
1036         private String artifactId;
1037 
1038         private String version;
1039 
1040         /**
1041          * @return the prefix
1042          */
1043         public String getPrefix()
1044         {
1045             return prefix;
1046         }
1047 
1048         /**
1049          * @param prefix the prefix to set
1050          */
1051         public void setPrefix( String prefix )
1052         {
1053             this.prefix = prefix;
1054         }
1055 
1056         /**
1057          * @return the groupId
1058          */
1059         public String getGroupId()
1060         {
1061             return groupId;
1062         }
1063 
1064         /**
1065          * @param groupId the groupId to set
1066          */
1067         public void setGroupId( String groupId )
1068         {
1069             this.groupId = groupId;
1070         }
1071 
1072         /**
1073          * @return the artifactId
1074          */
1075         public String getArtifactId()
1076         {
1077             return artifactId;
1078         }
1079 
1080         /**
1081          * @param artifactId the artifactId to set
1082          */
1083         public void setArtifactId( String artifactId )
1084         {
1085             this.artifactId = artifactId;
1086         }
1087 
1088         /**
1089          * @return the version
1090          */
1091         public String getVersion()
1092         {
1093             return version;
1094         }
1095 
1096         /**
1097          * @param version the version to set
1098          */
1099         public void setVersion( String version )
1100         {
1101             this.version = version;
1102         }
1103 
1104     }
1105 }