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