Coverage Report - org.apache.maven.tools.plugin.generator.PluginHelpGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
PluginHelpGenerator
40%
59/145
22%
9/40
4.083
 
 1  
 package org.apache.maven.tools.plugin.generator;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import org.apache.maven.plugin.descriptor.MojoDescriptor;
 23  
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
 24  
 import org.apache.maven.plugin.logging.Log;
 25  
 import org.apache.maven.project.MavenProject;
 26  
 import org.apache.maven.tools.plugin.PluginToolsRequest;
 27  
 import org.apache.velocity.VelocityContext;
 28  
 import org.codehaus.plexus.logging.AbstractLogEnabled;
 29  
 import org.codehaus.plexus.logging.Logger;
 30  
 import org.codehaus.plexus.logging.console.ConsoleLogger;
 31  
 import org.codehaus.plexus.util.FileUtils;
 32  
 import org.codehaus.plexus.util.IOUtil;
 33  
 import org.codehaus.plexus.util.PropertyUtils;
 34  
 import org.codehaus.plexus.util.StringUtils;
 35  
 import org.codehaus.plexus.velocity.VelocityComponent;
 36  
 import org.objectweb.asm.ClassReader;
 37  
 import org.objectweb.asm.ClassVisitor;
 38  
 import org.objectweb.asm.ClassWriter;
 39  
 import org.objectweb.asm.commons.Remapper;
 40  
 import org.objectweb.asm.commons.RemappingClassAdapter;
 41  
 import org.objectweb.asm.commons.SimpleRemapper;
 42  
 
 43  
 import java.io.File;
 44  
 import java.io.FileInputStream;
 45  
 import java.io.FileOutputStream;
 46  
 import java.io.IOException;
 47  
 import java.io.InputStream;
 48  
 import java.io.InputStreamReader;
 49  
 import java.io.OutputStreamWriter;
 50  
 import java.io.PrintWriter;
 51  
 import java.io.Reader;
 52  
 import java.io.StringWriter;
 53  
 import java.io.UnsupportedEncodingException;
 54  
 import java.util.List;
 55  
 import java.util.Properties;
 56  
 
 57  
 /**
 58  
  * Generates an <code>HelpMojo</code> class from <code>help-class-source.vm</code> template.
 59  
  * The generated mojo reads help content from <code>META-INF/maven/${groupId}/${artifactId}/plugin-help.xml</code> resource,
 60  
  * which is generated by this {@link PluginDescriptorGenerator}.
 61  
  * <p>Notice that the help mojo source needs to be generated before compilation, but when Java 5 annotations are used,
 62  
  * plugin descriptor content is available only after compilation (detecting annotations in .class files):
 63  
  * help mojo source can be generated with empty package only (and no plugin descriptor available yet), then needs
 64  
  * to be updated after compilation - through {@link #rewriteHelpMojo(PluginToolsRequest, Log)} which is called from plugin
 65  
  * descriptor XML generation.</p>
 66  
  *
 67  
  * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
 68  
  * @version $Id: PluginHelpGenerator.java 1406615 2012-11-07 13:26:25Z krosenvold $
 69  
  * @since 2.4
 70  
  */
 71  
 public class PluginHelpGenerator
 72  
     extends AbstractLogEnabled
 73  
     implements Generator
 74  
 {
 75  
     /**
 76  
      * Default generated class name
 77  
      */
 78  
     private static final String HELP_MOJO_CLASS_NAME = "HelpMojo";
 79  
 
 80  
     /**
 81  
      * Help properties file, to store data about generated source.
 82  
      */
 83  
     private static final String HELP_PROPERTIES_FILENAME = "maven-plugin-help.properties";
 84  
 
 85  
     /**
 86  
      * Default goal
 87  
      */
 88  
     private static final String HELP_GOAL = "help";
 89  
 
 90  
     private String helpPackageName;
 91  
 
 92  
     private boolean useAnnotations;
 93  
 
 94  
     private VelocityComponent velocityComponent;
 95  
 
 96  
     /**
 97  
      * Default constructor
 98  
      */
 99  
     public PluginHelpGenerator()
 100  1
     {
 101  1
         this.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "PluginHelpGenerator" ) );
 102  1
     }
 103  
 
 104  
     // ----------------------------------------------------------------------
 105  
     // Public methods
 106  
     // ----------------------------------------------------------------------
 107  
 
 108  
     /**
 109  
      * {@inheritDoc}
 110  
      */
 111  
     public void execute( File destinationDirectory, PluginToolsRequest request )
 112  
         throws GeneratorException
 113  
     {
 114  1
         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
 115  
 
 116  1
         String helpImplementation = getImplementation( pluginDescriptor );
 117  
 
 118  
         @SuppressWarnings( "unchecked" )
 119  1
         List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos();
 120  
 
 121  1
         if ( mojoDescriptors != null )
 122  
         {
 123  
             // Verify that no help goal already exists
 124  1
             MojoDescriptor descriptor = pluginDescriptor.getMojo( HELP_GOAL );
 125  
 
 126  1
             if ( ( descriptor != null ) && !descriptor.getImplementation().equals( helpImplementation ) )
 127  
             {
 128  0
                 if ( getLogger().isWarnEnabled() )
 129  
                 {
 130  0
                     getLogger().warn( "\n\nA help goal (" + descriptor.getImplementation()
 131  
                                           + ") already exists in this plugin. SKIPPED THE " + helpImplementation
 132  
                                           + " GENERATION.\n" );
 133  
                 }
 134  
 
 135  0
                 return;
 136  
             }
 137  
         }
 138  
 
 139  1
         writeHelpPropertiesFile( request, destinationDirectory );
 140  
         
 141  1
         useAnnotations = request.getProject().getArtifactMap().containsKey( "org.apache.maven.plugin-tools:maven-plugin-annotations" );
 142  
 
 143  
         try
 144  
         {
 145  1
             String sourcePath = helpImplementation.replace( '.', File.separatorChar ) + ".java";
 146  
 
 147  1
             File helpClass = new File( destinationDirectory, sourcePath );
 148  1
             helpClass.getParentFile().mkdirs();
 149  
 
 150  1
             String helpClassSources = getHelpClassSources( getPluginHelpPath( request.getProject() ), pluginDescriptor );
 151  
 
 152  1
             FileUtils.fileWrite( helpClass, request.getEncoding(), helpClassSources );
 153  
         }
 154  0
         catch ( IOException e )
 155  
         {
 156  0
             throw new GeneratorException( e.getMessage(), e );
 157  1
         }
 158  1
     }
 159  
 
 160  
     public PluginHelpGenerator setHelpPackageName( String helpPackageName )
 161  
     {
 162  0
         this.helpPackageName = helpPackageName;
 163  0
         return this;
 164  
     }
 165  
     
 166  
     public VelocityComponent getVelocityComponent()
 167  
     {
 168  0
         return velocityComponent;
 169  
     }
 170  
 
 171  
     public PluginHelpGenerator setVelocityComponent( VelocityComponent velocityComponent )
 172  
     {
 173  1
         this.velocityComponent = velocityComponent;
 174  1
         return this;
 175  
     }
 176  
 
 177  
     // ----------------------------------------------------------------------
 178  
     // Private methods
 179  
     // ----------------------------------------------------------------------
 180  
 
 181  
     private String getHelpClassSources( String pluginHelpPath, PluginDescriptor pluginDescriptor )
 182  
     {
 183  1
         Properties properties = new Properties();
 184  1
         VelocityContext context = new VelocityContext( properties );
 185  1
         if ( this.helpPackageName != null )
 186  
         {
 187  1
             properties.put( "helpPackageName", this.helpPackageName );
 188  
         }
 189  
         else
 190  
         {
 191  0
             properties.put( "helpPackageName", "" );
 192  
         }
 193  1
         properties.put( "pluginHelpPath", pluginHelpPath );
 194  1
         properties.put( "artifactId", pluginDescriptor.getArtifactId() );
 195  1
         properties.put( "goalPrefix", pluginDescriptor.getGoalPrefix() );
 196  1
         properties.put( "useAnnotations", useAnnotations );
 197  
 
 198  1
         StringWriter stringWriter = new StringWriter();
 199  
 
 200  1
         InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( "help-class-source.vm" );
 201  1
         InputStreamReader isReader = null;
 202  
         try
 203  
         {
 204  1
             isReader = new InputStreamReader( is, "UTF-8" ); // plugin-tools sources are UTF-8 (and even ASCII in this case)
 205  1
             velocityComponent.getEngine().evaluate( context, stringWriter, "", isReader );
 206  
         }
 207  0
         catch ( UnsupportedEncodingException e )
 208  
         {
 209  
             // not supposed to happen since UTF-8 is supposed to be supported by any JVM
 210  
         }
 211  
         finally
 212  
         {
 213  1
             IOUtil.close( is );
 214  1
             IOUtil.close( isReader );
 215  1
         }
 216  
 
 217  1
         return stringWriter.toString();
 218  
     }
 219  
 
 220  
     /**
 221  
      * @param pluginDescriptor The descriptor of the plugin for which to generate a help goal, must not be
 222  
      *                         <code>null</code>.
 223  
      * @return The implementation.
 224  
      */
 225  
     private String getImplementation( PluginDescriptor pluginDescriptor )
 226  
     {
 227  1
         if ( StringUtils.isEmpty( helpPackageName ) )
 228  
         {
 229  1
             helpPackageName = GeneratorUtils.discoverPackageName( pluginDescriptor );
 230  
         }
 231  
 
 232  1
         return StringUtils.isEmpty( helpPackageName ) ? HELP_MOJO_CLASS_NAME : helpPackageName + '.' + HELP_MOJO_CLASS_NAME;
 233  
     }
 234  
 
 235  
     /**
 236  
      * Write help properties files for later use to eventually rewrite Help Mojo.
 237  
      *
 238  
      * @param request
 239  
      * @throws GeneratorException
 240  
      * @see {@link #rewriteHelpMojo(PluginToolsRequest, Log)}
 241  
      */
 242  
     private void writeHelpPropertiesFile( PluginToolsRequest request, File destinationDirectory )
 243  
         throws GeneratorException
 244  
     {
 245  1
         Properties properties = new Properties();
 246  1
         properties.put( "helpPackageName", helpPackageName == null ? "" : helpPackageName );
 247  1
         properties.put( "destinationDirectory", destinationDirectory.getAbsolutePath() );
 248  
 
 249  1
         File tmpPropertiesFile =
 250  
             new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME );
 251  
 
 252  1
         if ( tmpPropertiesFile.exists() )
 253  
         {
 254  1
             tmpPropertiesFile.delete();
 255  
         }
 256  0
         else if ( !tmpPropertiesFile.getParentFile().exists() )
 257  
         {
 258  0
             tmpPropertiesFile.getParentFile().mkdirs();
 259  
         }
 260  
 
 261  1
         FileOutputStream fos = null;
 262  
         try
 263  
         {
 264  1
             fos = new FileOutputStream( tmpPropertiesFile );
 265  1
             properties.store( fos, "maven plugin help mojo generation informations" );
 266  
         }
 267  0
         catch ( IOException e )
 268  
         {
 269  0
             throw new GeneratorException( e.getMessage(), e );
 270  
         }
 271  
         finally
 272  
         {
 273  1
             IOUtil.close( fos );
 274  1
         }
 275  1
     }
 276  
 
 277  
     static String getPluginHelpPath( MavenProject mavenProject )
 278  
     {
 279  2
         return "META-INF/maven/" + mavenProject.getGroupId() + "/" + mavenProject.getArtifactId() + "/plugin-help.xml";
 280  
     }
 281  
 
 282  
     /**
 283  
      * Rewrite Help Mojo to match actual Mojos package name if it was not available at source generation
 284  
      * time. This is used at descriptor generation time.
 285  
      *
 286  
      * @param request
 287  
      * @throws GeneratorException
 288  
      */
 289  
     static void rewriteHelpMojo( PluginToolsRequest request, Log log )
 290  
         throws GeneratorException
 291  
     {
 292  1
         File tmpPropertiesFile =
 293  
             new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME );
 294  
 
 295  1
         if ( !tmpPropertiesFile.exists() )
 296  
         {
 297  0
             return;
 298  
         }
 299  
 
 300  1
         Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile );
 301  
 
 302  1
         String helpPackageName = properties.getProperty( "helpPackageName" );
 303  
 
 304  
         // if helpPackageName property is empty, we have to rewrite the class with a better package name than empty
 305  1
         if ( StringUtils.isEmpty( helpPackageName ) )
 306  
         {
 307  0
             String destDir = properties.getProperty( "destinationDirectory" );
 308  
             File destinationDirectory;
 309  0
             if ( StringUtils.isEmpty( destDir ) )
 310  
             {
 311  
                 // writeHelpPropertiesFile() creates 2 properties: find one without the other should not be possible
 312  0
                 log.warn( "\n\nUnexpected situation: destinationDirectory not defined in " + HELP_PROPERTIES_FILENAME
 313  
                     + " during help mojo source generation but expected during XML descriptor generation." );
 314  0
                 log.warn( "Please check helpmojo goal version used in previous build phase." );
 315  0
                 log.warn("If you just upgraded to plugin-tools >= 3.2 you must run a clean build at least once");
 316  0
                 destinationDirectory = new File( "target/generated-sources/plugin" );
 317  0
                 log.warn( "Trying default location: " + destinationDirectory );
 318  
             }
 319  
             else
 320  
             {
 321  0
                 destinationDirectory = new File( destDir );
 322  
             }
 323  0
             String helpMojoImplementation = rewriteHelpClassToMojoPackage( request, destinationDirectory, log );
 324  
 
 325  0
             if ( helpMojoImplementation != null )
 326  
             {
 327  
                 // rewrite plugin descriptor with new HelpMojo implementation class
 328  0
                 updateHelpMojoDescriptor( request.getPluginDescriptor(), helpMojoImplementation );
 329  
             }
 330  
         }
 331  1
     }
 332  
 
 333  
     private static String rewriteHelpClassToMojoPackage( PluginToolsRequest request, File destinationDirectory, Log log )
 334  
         throws GeneratorException
 335  
     {
 336  0
         String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() );
 337  0
         if ( StringUtils.isEmpty( destinationPackage ) )
 338  
         {
 339  0
             return null;
 340  
         }
 341  0
         String packageAsDirectory = StringUtils.replace( destinationPackage, '.', '/' );
 342  
 
 343  0
         String outputDirectory = request.getProject().getBuild().getOutputDirectory();
 344  0
         File helpClassFile = new File( outputDirectory, HELP_MOJO_CLASS_NAME + ".class" );
 345  0
         if ( !helpClassFile.exists() )
 346  
         {
 347  0
             return null;
 348  
         }
 349  
 
 350  
         // rewrite help mojo source
 351  0
         File helpSourceFile = new File( destinationDirectory, HELP_MOJO_CLASS_NAME + ".java" );
 352  0
         if ( !helpSourceFile.exists() )
 353  
         {
 354  0
             log.warn( "HelpMojo.java not found in default location: " + helpSourceFile.getAbsolutePath() );
 355  0
             log.warn( "Help goal source won't be moved to package: " + destinationPackage );
 356  
         }
 357  
         else
 358  
         {
 359  0
             File helpSourceFileNew = new File( destinationDirectory, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME + ".java" );
 360  0
             if ( !helpSourceFileNew.getParentFile().exists() )
 361  
             {
 362  0
                 helpSourceFileNew.getParentFile().mkdirs();
 363  
             }
 364  0
             Reader sourceReader = null;
 365  0
             PrintWriter sourceWriter = null;
 366  
             try
 367  
             {
 368  0
                 sourceReader = new InputStreamReader( new FileInputStream( helpSourceFile ), request.getEncoding() );
 369  0
                 sourceWriter =
 370  
                     new PrintWriter( new OutputStreamWriter( new FileOutputStream( helpSourceFileNew ),
 371  
                                                              request.getEncoding() ) );
 372  
     
 373  0
                 sourceWriter.println( "package " + destinationPackage + ";" );
 374  0
                 IOUtil.copy( sourceReader, sourceWriter );
 375  
             }
 376  0
             catch ( IOException e )
 377  
             {
 378  0
                 throw new GeneratorException( e.getMessage(), e );
 379  
             }
 380  
             finally
 381  
             {
 382  0
                 IOUtil.close( sourceReader );
 383  0
                 IOUtil.close( sourceWriter );
 384  0
             }
 385  0
             helpSourceFileNew.setLastModified( helpSourceFile.lastModified() );
 386  0
             helpSourceFile.delete();
 387  
         }
 388  
 
 389  
         // rewrite help mojo .class
 390  0
         File rewriteHelpClassFile =
 391  
             new File( outputDirectory + '/' + packageAsDirectory, HELP_MOJO_CLASS_NAME + ".class" );
 392  0
         if ( !rewriteHelpClassFile.getParentFile().exists() )
 393  
         {
 394  0
             rewriteHelpClassFile.getParentFile().mkdirs();
 395  
         }
 396  
 
 397  0
         FileInputStream fileInputStream = null;
 398  0
         ClassReader cr = null;
 399  
         try
 400  
         {
 401  0
             fileInputStream = new FileInputStream( helpClassFile );
 402  0
             cr = new ClassReader( fileInputStream );
 403  
         }
 404  0
         catch ( IOException e )
 405  
         {
 406  0
             throw new GeneratorException( e.getMessage(), e );
 407  
         }
 408  
         finally
 409  
         {
 410  0
             IOUtil.close( fileInputStream );
 411  0
         }
 412  
 
 413  0
         ClassWriter cw = new ClassWriter( 0 );
 414  
 
 415  0
         Remapper packageRemapper =
 416  
             new SimpleRemapper( HELP_MOJO_CLASS_NAME, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME );
 417  0
         ClassVisitor cv = new RemappingClassAdapter( cw, packageRemapper );
 418  
 
 419  
         try
 420  
         {
 421  0
             cr.accept( cv, ClassReader.EXPAND_FRAMES );
 422  
         }
 423  0
         catch ( Throwable e )
 424  
         {
 425  0
             throw new GeneratorException( "ASM issue processing class-file " + helpClassFile.getPath(), e );
 426  0
         }
 427  
 
 428  0
         byte[] renamedClass = cw.toByteArray();
 429  0
         FileOutputStream fos = null;
 430  
         try
 431  
         {
 432  0
             fos = new FileOutputStream( rewriteHelpClassFile );
 433  0
             fos.write( renamedClass );
 434  
         }
 435  0
         catch ( IOException e )
 436  
         {
 437  0
             throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e );
 438  
         }
 439  
         finally
 440  
         {
 441  0
             IOUtil.close( fos );
 442  0
         }
 443  
 
 444  0
         helpClassFile.delete();
 445  
 
 446  0
         return destinationPackage + ".HelpMojo";
 447  
     }
 448  
 
 449  
     private static void updateHelpMojoDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation )
 450  
     {
 451  0
         MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( HELP_GOAL );
 452  
 
 453  0
         if ( mojoDescriptor != null )
 454  
         {
 455  0
             mojoDescriptor.setImplementation( helpMojoImplementation );
 456  
         }
 457  0
     }
 458  
 }