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 }