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