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 <pre> mvn plugin:help -Ddetail=true -Dgoal=<goal-name></pre> 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 */ 032 //@Parameter( expression = "${detail}", defaultValue = "false" ) 033 //private boolean detail; 034 035 /** 036 * The name of the goal for which to show help. If unspecified, all goals will be displayed. 037 * 038 */ 039 //@Parameter( expression = "${goal}" ) 040 //private java.lang.String goal; 041 042 /** 043 * The maximum length of a display line, should be positive. 044 * 045 */ 046 //@Parameter( expression = "${lineLength}", defaultValue = "80" ) 047 //private int lineLength; 048 049 /** 050 * The number of spaces per indentation level, should be positive. 051 * 052 */ 053 //@Parameter( expression = "${indentSize}", defaultValue = "2" ) 054 //private int indentSize; 055 056 /** 057 * If <code>true</code>, display all settable properties for each goal. 058 * 059 * @parameter expression="${detail}" default-value="false" 060 */ 061 private boolean detail; 062 063 /** 064 * The name of the goal for which to show help. If unspecified, all goals will be displayed. 065 * 066 * @parameter expression="${goal}" 067 */ 068 private java.lang.String goal; 069 070 /** 071 * The maximum length of a display line, should be positive. 072 * 073 * @parameter expression="${lineLength}" default-value="80" 074 */ 075 private int lineLength; 076 077 /** 078 * The number of spaces per indentation level, should be positive. 079 * 080 * @parameter expression="${indentSize}" default-value="2" 081 */ 082 private int indentSize; 083 084 // groupId/artifactId/plugin-help.xml 085 private static final String PLUGIN_HELP_PATH = "/META-INF/maven/org.apache.maven.plugins/maven-plugin-plugin/plugin-help.xml"; 086 087 private Xpp3Dom build() 088 throws MojoExecutionException 089 { 090 getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH ); 091 InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH ); 092 try 093 { 094 return Xpp3DomBuilder.build( ReaderFactory.newXmlReader( is ) ); 095 } 096 catch ( XmlPullParserException e ) 097 { 098 throw new MojoExecutionException( e.getMessage(), e ); 099 } 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 }