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