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