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