View Javadoc

1   package net.nicoulaj.maven.plugins.soot;
2   
3   import org.apache.maven.plugin.AbstractMojo;
4   import org.apache.maven.plugin.MojoExecutionException;
5   import org.codehaus.plexus.util.ReaderFactory;
6   import org.codehaus.plexus.util.StringUtils;
7   import org.codehaus.plexus.util.xml.Xpp3Dom;
8   import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
9   import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
10  
11  import java.io.IOException;
12  import java.io.InputStream;
13  import java.util.ArrayList;
14  import java.util.Iterator;
15  import java.util.List;
16  
17  /**
18   * Display help information on soot-maven-plugin.<br/>
19   * Call <code>mvn soot:help -Ddetail=true -Dgoal=&lt;goal-name&gt;</code> to display parameter details.
20   * @author
21   * @version
22   * @goal help
23   * @requiresProject false
24   * @threadSafe
25   */
26  public class HelpMojo
27      extends AbstractMojo
28  {
29      /**
30       * If <code>true</code>, display all settable properties for each goal.
31       *
32       * @parameter property="detail" default-value="false"
33       */
34      //@Parameter( property = "detail", defaultValue = "false" )
35      private boolean detail;
36  
37      /**
38       * The name of the goal for which to show help. If unspecified, all goals will be displayed.
39       *
40       * @parameter property="goal"
41       */
42      //@Parameter( property = "goal" )
43      private java.lang.String goal;
44  
45      /**
46       * The maximum length of a display line, should be positive.
47       *
48       * @parameter property="lineLength" default-value="80"
49       */
50      //@Parameter( property = "lineLength", defaultValue = "80" )
51      private int lineLength;
52  
53      /**
54       * The number of spaces per indentation level, should be positive.
55       *
56       * @parameter property="indentSize" default-value="2"
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/net.ju-n.maven.plugins/soot-maven-plugin/plugin-help.xml";
63  
64      private Xpp3Dom build()
65          throws MojoExecutionException
66      {
67          getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH );
68          InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH );
69          try
70          {
71              return Xpp3DomBuilder.build( ReaderFactory.newXmlReader( is ) );
72          }
73          catch ( XmlPullParserException e )
74          {
75              throw new MojoExecutionException( e.getMessage(), e );
76          }
77          catch ( IOException e )
78          {
79              throw new MojoExecutionException( e.getMessage(), e );
80          }
81      }
82  
83      /**
84       * {@inheritDoc}
85       */
86      public void execute()
87          throws MojoExecutionException
88      {
89          if ( lineLength <= 0 )
90          {
91              getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." );
92              lineLength = 80;
93          }
94          if ( indentSize <= 0 )
95          {
96              getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." );
97              indentSize = 2;
98          }
99  
100         Xpp3Dom pluginElement = build();
101 
102         StringBuilder sb = new StringBuilder();
103         String name = pluginElement.getChild( "name" ).getValue();
104         String version = pluginElement.getChild( "version" ).getValue();
105         String id = pluginElement.getChild( "groupId" ).getValue() + ":" + pluginElement.getChild( "artifactId" ).getValue()
106                     + ":" + version;
107         if ( StringUtils.isNotEmpty( name ) && !name.contains( id ) )
108         {
109             append( sb, name + " " + version, 0 );
110         }
111         else
112         {
113             if ( StringUtils.isNotEmpty( name ) )
114             {
115                 append( sb, name, 0 );
116             }
117             else
118             {
119                 append( sb, id, 0 );
120             }
121         }
122         append( sb, pluginElement.getChild( "description" ).getValue(), 1 );
123         append( sb, "", 0 );
124 
125         //<goalPrefix>plugin</goalPrefix>
126         String goalPrefix = pluginElement.getChild( "goalPrefix" ).getValue();
127 
128         Xpp3Dom[] mojos = pluginElement.getChild( "mojos" ).getChildren( "mojo" );
129 
130         if ( goal == null || goal.length() <= 0 )
131         {
132             append( sb, "This plugin has " + mojos.length + ( mojos.length > 1 ? " goals:" : " goal:" ) , 0 );
133             append( sb, "", 0 );
134         }
135 
136         for ( Xpp3Dom mojo : mojos )
137         {
138             writeGoal( sb, goalPrefix, mojo );
139         }
140 
141         if ( getLog().isInfoEnabled() )
142         {
143             getLog().info( sb.toString() );
144         }
145     }
146 
147     private String getValue( Xpp3Dom mojo, String child )
148     {
149         Xpp3Dom elt = mojo.getChild( child );
150         return ( elt == null ) ? "" : elt.getValue();
151     }
152 
153     private void writeGoal( StringBuilder sb, String goalPrefix, Xpp3Dom mojo )
154     {
155         String mojoGoal = mojo.getChild( "goal" ).getValue();
156         Xpp3Dom configurationElement = mojo.getChild( "configuration" );
157 
158         if ( goal == null || goal.length() <= 0 || mojoGoal.equals( goal ) )
159         {
160             append( sb, goalPrefix + ":" + mojoGoal, 0 );
161             Xpp3Dom deprecated = mojo.getChild( "deprecated" );
162             if ( ( deprecated != null ) && StringUtils.isNotEmpty( deprecated.getValue() ) )
163             {
164                 append( sb, "Deprecated. " + deprecated, 1 );
165                 if ( detail )
166                 {
167                     append( sb, "", 0 );
168                     append( sb, getValue( mojo, "description" ), 1 );
169                 }
170             }
171             else
172             {
173                 append( sb, getValue( mojo, "description" ), 1 );
174             }
175             append( sb, "", 0 );
176 
177             if ( detail )
178             {
179                 Xpp3Dom[] parameters = mojo.getChild( "parameters" ).getChildren( "parameter" );
180                 append( sb, "Available parameters:", 1 );
181                 append( sb, "", 0 );
182 
183                 for ( Xpp3Dom parameter : parameters )
184                 {
185                     writeParameter( sb, parameter, configurationElement );
186                 }
187             }
188         }
189     }
190 
191     private void writeParameter( StringBuilder sb, Xpp3Dom parameter, Xpp3Dom configurationElement )
192     {
193         String parameterName = parameter.getChild( "name" ).getValue();
194         String parameterDescription = parameter.getChild( "description" ).getValue();
195 
196         Xpp3Dom fieldConfigurationElement = configurationElement.getChild( parameterName );
197 
198         String parameterDefaultValue = "";
199         if ( fieldConfigurationElement != null && fieldConfigurationElement.getValue() != null )
200         {
201             parameterDefaultValue = " (Default: " + fieldConfigurationElement.getAttribute( "default-value" ) + ")";
202         }
203         append( sb, parameterName + parameterDefaultValue, 2 );
204         Xpp3Dom deprecated = parameter.getChild( "deprecated" );
205         if ( ( deprecated != null ) && StringUtils.isNotEmpty( deprecated.getValue() ) )
206         {
207             append( sb, "Deprecated. " + deprecated.getValue(), 3 );
208             append( sb, "", 0 );
209         }
210         append( sb, parameterDescription, 3 );
211         if ( "true".equals( parameter.getChild( "required" ).getValue() ) )
212         {
213             append( sb, "Required: Yes", 3 );
214         }
215         Xpp3Dom expression = parameter.getChild( "expression" );
216         if ( ( expression != null ) && StringUtils.isNotEmpty( expression.getValue() ) )
217         {
218             append( sb, "Expression: " + expression.getValue(), 3 );
219         }
220 
221         append( sb, "", 0 );
222     }
223 
224     /**
225      * <p>Repeat a String <code>n</code> times to form a new string.</p>
226      *
227      * @param str    String to repeat
228      * @param repeat number of times to repeat str
229      * @return String with repeated String
230      * @throws NegativeArraySizeException if <code>repeat < 0</code>
231      * @throws NullPointerException       if str is <code>null</code>
232      */
233     private static String repeat( String str, int repeat )
234     {
235         StringBuilder buffer = new StringBuilder( repeat * str.length() );
236 
237         for ( int i = 0; i < repeat; i++ )
238         {
239             buffer.append( str );
240         }
241 
242         return buffer.toString();
243     }
244 
245     /**
246      * Append a description to the buffer by respecting the indentSize and lineLength parameters.
247      * <b>Note</b>: The last character is always a new line.
248      *
249      * @param sb          The buffer to append the description, not <code>null</code>.
250      * @param description The description, not <code>null</code>.
251      * @param indent      The base indentation level of each line, must not be negative.
252      */
253     private void append( StringBuilder sb, String description, int indent )
254     {
255         for ( String line : toLines( description, indent, indentSize, lineLength ) )
256         {
257             sb.append( line ).append( '\n' );
258         }
259     }
260 
261     /**
262      * Splits the specified text into lines of convenient display length.
263      *
264      * @param text       The text to split into lines, must not be <code>null</code>.
265      * @param indent     The base indentation level of each line, must not be negative.
266      * @param indentSize The size of each indentation, must not be negative.
267      * @param lineLength The length of the line, must not be negative.
268      * @return The sequence of display lines, never <code>null</code>.
269      * @throws NegativeArraySizeException if <code>indent < 0</code>
270      */
271     private static List<String> toLines( String text, int indent, int indentSize, int lineLength )
272     {
273         List<String> lines = new ArrayList<String>();
274 
275         String ind = repeat( "\t", indent );
276 
277         String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" );
278 
279         for ( String plainLine : plainLines )
280         {
281             toLines( lines, ind + plainLine, indentSize, lineLength );
282         }
283 
284         return lines;
285     }
286 
287     /**
288      * Adds the specified line to the output sequence, performing line wrapping if necessary.
289      *
290      * @param lines      The sequence of display lines, must not be <code>null</code>.
291      * @param line       The line to add, must not be <code>null</code>.
292      * @param indentSize The size of each indentation, must not be negative.
293      * @param lineLength The length of the line, must not be negative.
294      */
295     private static void toLines( List<String> lines, String line, int indentSize, int lineLength )
296     {
297         int lineIndent = getIndentLevel( line );
298         StringBuilder buf = new StringBuilder( 256 );
299 
300         String[] tokens = line.split( " +" );
301 
302         for ( String token : tokens )
303         {
304             if ( buf.length() > 0 )
305             {
306                 if ( buf.length() + token.length() >= lineLength )
307                 {
308                     lines.add( buf.toString() );
309                     buf.setLength( 0 );
310                     buf.append( repeat( " ", lineIndent * indentSize ) );
311                 }
312                 else
313                 {
314                     buf.append( ' ' );
315                 }
316             }
317 
318             for ( int j = 0; j < token.length(); j++ )
319             {
320                 char c = token.charAt( j );
321                 if ( c == '\t' )
322                 {
323                     buf.append( repeat( " ", indentSize - buf.length() % indentSize ) );
324                 }
325                 else if ( c == '\u00A0' )
326                 {
327                     buf.append( ' ' );
328                 }
329                 else
330                 {
331                     buf.append( c );
332                 }
333             }
334         }
335         lines.add( buf.toString() );
336     }
337 
338     /**
339      * Gets the indentation level of the specified line.
340      *
341      * @param line The line whose indentation level should be retrieved, must not be <code>null</code>.
342      * @return The indentation level of the line.
343      */
344     private static int getIndentLevel( String line )
345     {
346         int level = 0;
347         for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ )
348         {
349             level++;
350         }
351         for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ )
352         {
353             if ( line.charAt( i ) == '\t' )
354             {
355                 level++;
356                 break;
357             }
358         }
359         return level;
360     }
361 }