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=&lt;goal-name&gt;</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    }