001
002 import org.apache.maven.plugin.AbstractMojo;
003 import org.apache.maven.plugin.MojoExecutionException;
004 import org.codehaus.plexus.util.ReaderFactory;
005 import org.codehaus.plexus.util.StringUtils;
006 import org.codehaus.plexus.util.xml.Xpp3Dom;
007 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
008 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
009
010 import java.io.IOException;
011 import java.io.InputStream;
012 import java.util.ArrayList;
013 import java.util.Iterator;
014 import java.util.List;
015
016 /**
017 * Display help information on maven-plugin-plugin.<br/>
018 * Call <code>mvn plugin:help -Ddetail=true -Dgoal=<goal-name></code> to display parameter details.
019 * @author
020 * @version
021 * @goal help
022 * @requiresProject false
023 * @threadSafe
024 */
025 public class HelpMojo
026 extends AbstractMojo
027 {
028 /**
029 * If <code>true</code>, display all settable properties for each goal.
030 *
031 * @parameter property="detail" default-value="false"
032 */
033 //@Parameter( property = "detail", defaultValue = "false" )
034 private boolean detail;
035
036 /**
037 * The name of the goal for which to show help. If unspecified, all goals will be displayed.
038 *
039 * @parameter property="goal"
040 */
041 //@Parameter( property = "goal" )
042 private java.lang.String goal;
043
044 /**
045 * The maximum length of a display line, should be positive.
046 *
047 * @parameter property="lineLength" default-value="80"
048 */
049 //@Parameter( property = "lineLength", defaultValue = "80" )
050 private int lineLength;
051
052 /**
053 * The number of spaces per indentation level, should be positive.
054 *
055 * @parameter property="indentSize" default-value="2"
056 */
057 //@Parameter( property = "indentSize", defaultValue = "2" )
058 private int indentSize;
059
060 // groupId/artifactId/plugin-help.xml
061 private static final String PLUGIN_HELP_PATH = "/META-INF/maven/org.apache.maven.plugins/maven-plugin-plugin/plugin-help.xml";
062
063 private Xpp3Dom build()
064 throws MojoExecutionException
065 {
066 getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH );
067 InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH );
068 try
069 {
070 return Xpp3DomBuilder.build( ReaderFactory.newXmlReader( is ) );
071 }
072 catch ( XmlPullParserException e )
073 {
074 throw new MojoExecutionException( e.getMessage(), e );
075 }
076 catch ( IOException e )
077 {
078 throw new MojoExecutionException( e.getMessage(), e );
079 }
080 }
081
082 /**
083 * {@inheritDoc}
084 */
085 public void execute()
086 throws MojoExecutionException
087 {
088 if ( lineLength <= 0 )
089 {
090 getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." );
091 lineLength = 80;
092 }
093 if ( indentSize <= 0 )
094 {
095 getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." );
096 indentSize = 2;
097 }
098
099 Xpp3Dom pluginElement = build();
100
101 StringBuilder sb = new StringBuilder();
102 String name = pluginElement.getChild( "name" ).getValue();
103 String version = pluginElement.getChild( "version" ).getValue();
104 String id = pluginElement.getChild( "groupId" ).getValue() + ":" + pluginElement.getChild( "artifactId" ).getValue()
105 + ":" + version;
106 if ( StringUtils.isNotEmpty( name ) && !name.contains( id ) )
107 {
108 append( sb, name + " " + version, 0 );
109 }
110 else
111 {
112 if ( StringUtils.isNotEmpty( name ) )
113 {
114 append( sb, name, 0 );
115 }
116 else
117 {
118 append( sb, id, 0 );
119 }
120 }
121 append( sb, pluginElement.getChild( "description" ).getValue(), 1 );
122 append( sb, "", 0 );
123
124 //<goalPrefix>plugin</goalPrefix>
125 String goalPrefix = pluginElement.getChild( "goalPrefix" ).getValue();
126
127 Xpp3Dom[] mojos = pluginElement.getChild( "mojos" ).getChildren( "mojo" );
128
129 if ( goal == null || goal.length() <= 0 )
130 {
131 append( sb, "This plugin has " + mojos.length + ( mojos.length > 1 ? " goals:" : " goal:" ) , 0 );
132 append( sb, "", 0 );
133 }
134
135 for ( Xpp3Dom mojo : mojos )
136 {
137 writeGoal( sb, goalPrefix, mojo );
138 }
139
140 if ( getLog().isInfoEnabled() )
141 {
142 getLog().info( sb.toString() );
143 }
144 }
145
146 private String getValue( Xpp3Dom mojo, String child )
147 {
148 Xpp3Dom elt = mojo.getChild( child );
149 return ( elt == null ) ? "" : elt.getValue();
150 }
151
152 private void writeGoal( StringBuilder sb, String goalPrefix, Xpp3Dom mojo )
153 {
154 String mojoGoal = mojo.getChild( "goal" ).getValue();
155 Xpp3Dom configurationElement = mojo.getChild( "configuration" );
156
157 if ( goal == null || goal.length() <= 0 || mojoGoal.equals( goal ) )
158 {
159 append( sb, goalPrefix + ":" + mojoGoal, 0 );
160 Xpp3Dom deprecated = mojo.getChild( "deprecated" );
161 if ( ( deprecated != null ) && StringUtils.isNotEmpty( deprecated.getValue() ) )
162 {
163 append( sb, "Deprecated. " + deprecated, 1 );
164 if ( detail )
165 {
166 append( sb, "", 0 );
167 append( sb, getValue( mojo, "description" ), 1 );
168 }
169 }
170 else
171 {
172 append( sb, getValue( mojo, "description" ), 1 );
173 }
174 append( sb, "", 0 );
175
176 if ( detail )
177 {
178 Xpp3Dom[] parameters = mojo.getChild( "parameters" ).getChildren( "parameter" );
179 append( sb, "Available parameters:", 1 );
180 append( sb, "", 0 );
181
182 for ( Xpp3Dom parameter : parameters )
183 {
184 writeParameter( sb, parameter, configurationElement );
185 }
186 }
187 }
188 }
189
190 private void writeParameter( StringBuilder sb, Xpp3Dom parameter, Xpp3Dom configurationElement )
191 {
192 String parameterName = parameter.getChild( "name" ).getValue();
193 String parameterDescription = parameter.getChild( "description" ).getValue();
194
195 Xpp3Dom fieldConfigurationElement = configurationElement.getChild( parameterName );
196
197 String parameterDefaultValue = "";
198 if ( fieldConfigurationElement != null && fieldConfigurationElement.getValue() != null )
199 {
200 parameterDefaultValue = " (Default: " + fieldConfigurationElement.getAttribute( "default-value" ) + ")";
201 }
202 append( sb, parameterName + parameterDefaultValue, 2 );
203 Xpp3Dom deprecated = parameter.getChild( "deprecated" );
204 if ( ( deprecated != null ) && StringUtils.isNotEmpty( deprecated.getValue() ) )
205 {
206 append( sb, "Deprecated. " + deprecated.getValue(), 3 );
207 append( sb, "", 0 );
208 }
209 append( sb, parameterDescription, 3 );
210 if ( "true".equals( parameter.getChild( "required" ).getValue() ) )
211 {
212 append( sb, "Required: Yes", 3 );
213 }
214 Xpp3Dom expression = parameter.getChild( "expression" );
215 if ( ( expression != null ) && StringUtils.isNotEmpty( expression.getValue() ) )
216 {
217 append( sb, "Expression: " + expression.getValue(), 3 );
218 }
219
220 append( sb, "", 0 );
221 }
222
223 /**
224 * <p>Repeat a String <code>n</code> times to form a new string.</p>
225 *
226 * @param str String to repeat
227 * @param repeat number of times to repeat str
228 * @return String with repeated String
229 * @throws NegativeArraySizeException if <code>repeat < 0</code>
230 * @throws NullPointerException if str is <code>null</code>
231 */
232 private static String repeat( String str, int repeat )
233 {
234 StringBuilder buffer = new StringBuilder( repeat * str.length() );
235
236 for ( int i = 0; i < repeat; i++ )
237 {
238 buffer.append( str );
239 }
240
241 return buffer.toString();
242 }
243
244 /**
245 * Append a description to the buffer by respecting the indentSize and lineLength parameters.
246 * <b>Note</b>: The last character is always a new line.
247 *
248 * @param sb The buffer to append the description, not <code>null</code>.
249 * @param description The description, not <code>null</code>.
250 * @param indent The base indentation level of each line, must not be negative.
251 */
252 private void append( StringBuilder sb, String description, int indent )
253 {
254 for ( String line : toLines( description, indent, indentSize, lineLength ) )
255 {
256 sb.append( line ).append( '\n' );
257 }
258 }
259
260 /**
261 * Splits the specified text into lines of convenient display length.
262 *
263 * @param text The text to split into lines, must not be <code>null</code>.
264 * @param indent The base indentation level of each line, must not be negative.
265 * @param indentSize The size of each indentation, must not be negative.
266 * @param lineLength The length of the line, must not be negative.
267 * @return The sequence of display lines, never <code>null</code>.
268 * @throws NegativeArraySizeException if <code>indent < 0</code>
269 */
270 private static List<String> toLines( String text, int indent, int indentSize, int lineLength )
271 {
272 List<String> lines = new ArrayList<String>();
273
274 String ind = repeat( "\t", indent );
275
276 String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" );
277
278 for ( String plainLine : plainLines )
279 {
280 toLines( lines, ind + plainLine, indentSize, lineLength );
281 }
282
283 return lines;
284 }
285
286 /**
287 * Adds the specified line to the output sequence, performing line wrapping if necessary.
288 *
289 * @param lines The sequence of display lines, must not be <code>null</code>.
290 * @param line The line to add, must not be <code>null</code>.
291 * @param indentSize The size of each indentation, must not be negative.
292 * @param lineLength The length of the line, must not be negative.
293 */
294 private static void toLines( List<String> lines, String line, int indentSize, int lineLength )
295 {
296 int lineIndent = getIndentLevel( line );
297 StringBuilder buf = new StringBuilder( 256 );
298
299 String[] tokens = line.split( " +" );
300
301 for ( String token : tokens )
302 {
303 if ( buf.length() > 0 )
304 {
305 if ( buf.length() + token.length() >= lineLength )
306 {
307 lines.add( buf.toString() );
308 buf.setLength( 0 );
309 buf.append( repeat( " ", lineIndent * indentSize ) );
310 }
311 else
312 {
313 buf.append( ' ' );
314 }
315 }
316
317 for ( int j = 0; j < token.length(); j++ )
318 {
319 char c = token.charAt( j );
320 if ( c == '\t' )
321 {
322 buf.append( repeat( " ", indentSize - buf.length() % indentSize ) );
323 }
324 else if ( c == '\u00A0' )
325 {
326 buf.append( ' ' );
327 }
328 else
329 {
330 buf.append( c );
331 }
332 }
333 }
334 lines.add( buf.toString() );
335 }
336
337 /**
338 * Gets the indentation level of the specified line.
339 *
340 * @param line The line whose indentation level should be retrieved, must not be <code>null</code>.
341 * @return The indentation level of the line.
342 */
343 private static int getIndentLevel( String line )
344 {
345 int level = 0;
346 for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ )
347 {
348 level++;
349 }
350 for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ )
351 {
352 if ( line.charAt( i ) == '\t' )
353 {
354 level++;
355 break;
356 }
357 }
358 return level;
359 }
360 }