View Javadoc

1   
2   import org.apache.maven.plugin.AbstractMojo;
3   import org.apache.maven.plugin.MojoExecutionException;
4   import org.codehaus.plexus.util.ReaderFactory;
5   import org.codehaus.plexus.util.StringUtils;
6   import org.codehaus.plexus.util.xml.Xpp3Dom;
7   import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
8   import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
9   
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.util.ArrayList;
13  import java.util.Iterator;
14  import java.util.List;
15  
16  /**
17   * Display help information on maven-plugin-plugin.<br/>
18   * Call <pre> mvn plugin:help -Ddetail=true -Dgoal=&lt;goal-name&gt;</pre> to display parameter details.
19   * @author
20   * @version
21   * @goal help
22   * @requiresProject false
23   * @threadSafe
24   */
25  public class HelpMojo
26      extends AbstractMojo
27  {
28      /**
29       * If <code>true</code>, display all settable properties for each goal.
30       *
31       */
32      //@Parameter( expression = "${detail}", defaultValue = "false" )
33      //private boolean detail;
34  
35      /**
36       * The name of the goal for which to show help. If unspecified, all goals will be displayed.
37       *
38       */
39      //@Parameter( expression = "${goal}" )
40      //private java.lang.String goal;
41  
42      /**
43       * The maximum length of a display line, should be positive.
44       *
45       */
46      //@Parameter( expression = "${lineLength}", defaultValue = "80" )
47      //private int lineLength;
48  
49      /**
50       * The number of spaces per indentation level, should be positive.
51       *
52       */
53      //@Parameter( expression = "${indentSize}", defaultValue = "2" )
54      //private int indentSize;
55  
56      /**
57       * If <code>true</code>, display all settable properties for each goal.
58       *
59       * @parameter expression="${detail}" default-value="false"
60       */
61      private boolean detail;
62  
63      /**
64       * The name of the goal for which to show help. If unspecified, all goals will be displayed.
65       *
66       * @parameter expression="${goal}"
67       */
68      private java.lang.String goal;
69  
70      /**
71       * The maximum length of a display line, should be positive.
72       *
73       * @parameter expression="${lineLength}" default-value="80"
74       */
75      private int lineLength;
76  
77      /**
78       * The number of spaces per indentation level, should be positive.
79       *
80       * @parameter expression="${indentSize}" default-value="2"
81       */
82      private int indentSize;
83  
84      // groupId/artifactId/plugin-help.xml
85      private static final String PLUGIN_HELP_PATH = "/META-INF/maven/org.apache.maven.plugins/maven-plugin-plugin/plugin-help.xml";
86  
87      private Xpp3Dom build()
88          throws MojoExecutionException
89      {
90          getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH );
91          InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH );
92          try
93          {
94              return Xpp3DomBuilder.build( ReaderFactory.newXmlReader( is ) );
95          }
96          catch ( XmlPullParserException e )
97          {
98              throw new MojoExecutionException( e.getMessage(), e );
99          }
100         catch ( IOException e )
101         {
102             throw new MojoExecutionException( e.getMessage(), e );
103         }
104     }
105 
106     /**
107      * {@inheritDoc}
108      */
109     public void execute()
110         throws MojoExecutionException
111     {
112         if ( lineLength <= 0 )
113         {
114             getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." );
115             lineLength = 80;
116         }
117         if ( indentSize <= 0 )
118         {
119             getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." );
120             indentSize = 2;
121         }
122 
123         Xpp3Dom pluginElement = build();
124 
125         StringBuilder sb = new StringBuilder();
126         String name = pluginElement.getChild( "name" ).getValue();
127         String version = pluginElement.getChild( "version" ).getValue();
128         String id = pluginElement.getChild( "groupId" ).getValue() + ":" + pluginElement.getChild( "artifactId" ).getValue()
129                     + ":" + version;
130         if ( StringUtils.isNotEmpty( name ) && !name.contains( id ) )
131         {
132             append( sb, name + " " + version, 0 );
133         }
134         else
135         {
136             if ( StringUtils.isNotEmpty( name ) )
137             {
138                 append( sb, name, 0 );
139             }
140             else
141             {
142                 append( sb, id, 0 );
143             }
144         }
145         append( sb, pluginElement.getChild( "description" ).getValue(), 1 );
146         append( sb, "", 0 );
147 
148         //<goalPrefix>plugin</goalPrefix>
149         String goalPrefix = pluginElement.getChild( "goalPrefix" ).getValue();
150 
151         Xpp3Dom[] mojos = pluginElement.getChild( "mojos" ).getChildren( "mojo" );
152 
153         if ( goal == null || goal.length() <= 0 )
154         {
155             append( sb, "This plugin has " + mojos.length + ( mojos.length > 1 ? " goals:" : " goal:" ) , 0 );
156             append( sb, "", 0 );
157         }
158 
159         for ( Xpp3Dom mojo : mojos )
160         {
161             writeGoal( sb, goalPrefix, mojo );
162         }
163 
164         if ( getLog().isInfoEnabled() )
165         {
166             getLog().info( sb.toString() );
167         }
168     }
169 
170     private void writeGoal( StringBuilder sb, String goalPrefix, Xpp3Dom mojo )
171     {
172         String mojoGoal = mojo.getChild( "goal" ).getValue();
173         Xpp3Dom configurationElement = mojo.getChild( "configuration" );
174 
175         if ( goal == null || goal.length() <= 0 || mojoGoal.equals( goal ) )
176         {
177             append( sb, goalPrefix + ":" + mojoGoal, 0 );
178             Xpp3Dom deprecated = mojo.getChild( "deprecated" );
179             if ( ( deprecated != null ) && StringUtils.isNotEmpty( deprecated.getValue() ) )
180             {
181                 append( sb, "Deprecated. " + deprecated, 1 );
182                 if ( detail )
183                 {
184                     append( sb, "", 0 );
185                     append( sb, mojo.getChild( "description" ).getValue(), 1 );
186                 }
187             }
188             else
189             {
190                 append( sb, mojo.getChild( "description" ).getValue(), 1 );
191             }
192             append( sb, "", 0 );
193 
194             if ( detail )
195             {
196                 Xpp3Dom[] parameters = mojo.getChild( "parameters" ).getChildren( "parameter" );
197                 append( sb, "Available parameters:", 1 );
198                 append( sb, "", 0 );
199 
200                 for ( Xpp3Dom parameter : parameters )
201                 {
202                     writeParameter( sb, parameter, configurationElement );
203                 }
204             }
205         }
206     }
207 
208     private void writeParameter( StringBuilder sb, Xpp3Dom parameter, Xpp3Dom configurationElement )
209     {
210         String parameterName = parameter.getChild( "name" ).getValue();
211         String parameterDescription = parameter.getChild( "description" ).getValue();
212 
213         Xpp3Dom fieldConfigurationElement = configurationElement.getChild( parameterName );
214 
215         String parameterDefaultValue = "";
216         if ( fieldConfigurationElement != null && fieldConfigurationElement.getValue() != null )
217         {
218             parameterDefaultValue = " (Default: " + fieldConfigurationElement.getAttribute( "default-value" ) + ")";
219         }
220         append( sb, parameterName + parameterDefaultValue, 2 );
221         Xpp3Dom deprecated = parameter.getChild( "deprecated" );
222         if ( ( deprecated != null ) && StringUtils.isNotEmpty( deprecated.getValue() ) )
223         {
224             append( sb, "Deprecated. " + deprecated.getValue(), 3 );
225             append( sb, "", 0 );
226         }
227         append( sb, parameterDescription, 3 );
228         if ( "true".equals( parameter.getChild( "required" ).getValue() ) )
229         {
230             append( sb, "Required: Yes", 3 );
231         }
232         Xpp3Dom expression = parameter.getChild( "expression" );
233         if ( ( expression != null ) && StringUtils.isNotEmpty( expression.getValue() ) )
234         {
235             append( sb, "Expression: " + expression.getValue(), 3 );
236         }
237 
238         append( sb, "", 0 );
239     }
240 
241     /**
242      * <p>Repeat a String <code>n</code> times to form a new string.</p>
243      *
244      * @param str    String to repeat
245      * @param repeat number of times to repeat str
246      * @return String with repeated String
247      * @throws NegativeArraySizeException if <code>repeat < 0</code>
248      * @throws NullPointerException       if str is <code>null</code>
249      */
250     private static String repeat( String str, int repeat )
251     {
252         StringBuilder buffer = new StringBuilder( repeat * str.length() );
253 
254         for ( int i = 0; i < repeat; i++ )
255         {
256             buffer.append( str );
257         }
258 
259         return buffer.toString();
260     }
261 
262     /**
263      * Append a description to the buffer by respecting the indentSize and lineLength parameters.
264      * <b>Note</b>: The last character is always a new line.
265      *
266      * @param sb          The buffer to append the description, not <code>null</code>.
267      * @param description The description, not <code>null</code>.
268      * @param indent      The base indentation level of each line, must not be negative.
269      */
270     private void append( StringBuilder sb, String description, int indent )
271     {
272         for ( String line : toLines( description, indent, indentSize, lineLength ) )
273         {
274             sb.append( line ).append( '\n' );
275         }
276     }
277 
278     /**
279      * Splits the specified text into lines of convenient display length.
280      *
281      * @param text       The text to split into lines, must not be <code>null</code>.
282      * @param indent     The base indentation level of each line, must not be negative.
283      * @param indentSize The size of each indentation, must not be negative.
284      * @param lineLength The length of the line, must not be negative.
285      * @return The sequence of display lines, never <code>null</code>.
286      * @throws NegativeArraySizeException if <code>indent < 0</code>
287      */
288     private static List<String> toLines( String text, int indent, int indentSize, int lineLength )
289     {
290         List<String> lines = new ArrayList<String>();
291 
292         String ind = repeat( "\t", indent );
293 
294         String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" );
295 
296         for ( String plainLine : plainLines )
297         {
298             toLines( lines, ind + plainLine, indentSize, lineLength );
299         }
300 
301         return lines;
302     }
303 
304     /**
305      * Adds the specified line to the output sequence, performing line wrapping if necessary.
306      *
307      * @param lines      The sequence of display lines, must not be <code>null</code>.
308      * @param line       The line to add, must not be <code>null</code>.
309      * @param indentSize The size of each indentation, must not be negative.
310      * @param lineLength The length of the line, must not be negative.
311      */
312     private static void toLines( List<String> lines, String line, int indentSize, int lineLength )
313     {
314         int lineIndent = getIndentLevel( line );
315         StringBuilder buf = new StringBuilder( 256 );
316 
317         String[] tokens = line.split( " +" );
318 
319         for ( String token : tokens )
320         {
321             if ( buf.length() > 0 )
322             {
323                 if ( buf.length() + token.length() >= lineLength )
324                 {
325                     lines.add( buf.toString() );
326                     buf.setLength( 0 );
327                     buf.append( repeat( " ", lineIndent * indentSize ) );
328                 }
329                 else
330                 {
331                     buf.append( ' ' );
332                 }
333             }
334 
335             for ( int j = 0; j < token.length(); j++ )
336             {
337                 char c = token.charAt( j );
338                 if ( c == '\t' )
339                 {
340                     buf.append( repeat( " ", indentSize - buf.length() % indentSize ) );
341                 }
342                 else if ( c == '\u00A0' )
343                 {
344                     buf.append( ' ' );
345                 }
346                 else
347                 {
348                     buf.append( c );
349                 }
350             }
351         }
352         lines.add( buf.toString() );
353     }
354 
355     /**
356      * Gets the indentation level of the specified line.
357      *
358      * @param line The line whose indentation level should be retrieved, must not be <code>null</code>.
359      * @return The indentation level of the line.
360      */
361     private static int getIndentLevel( String line )
362     {
363         int level = 0;
364         for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ )
365         {
366             level++;
367         }
368         for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ )
369         {
370             if ( line.charAt( i ) == '\t' )
371             {
372                 level++;
373                 break;
374             }
375         }
376         return level;
377     }
378 }