001 package org.apache.maven.tools.plugin.generator; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import org.apache.maven.plugin.descriptor.MojoDescriptor; 023 import org.apache.maven.plugin.descriptor.PluginDescriptor; 024 import org.apache.maven.project.MavenProject; 025 import org.apache.maven.tools.plugin.PluginToolsRequest; 026 import org.apache.velocity.VelocityContext; 027 import org.codehaus.plexus.logging.AbstractLogEnabled; 028 import org.codehaus.plexus.logging.Logger; 029 import org.codehaus.plexus.logging.console.ConsoleLogger; 030 import org.codehaus.plexus.util.FileUtils; 031 import org.codehaus.plexus.util.IOUtil; 032 import org.codehaus.plexus.util.PropertyUtils; 033 import org.codehaus.plexus.util.StringUtils; 034 import org.codehaus.plexus.velocity.VelocityComponent; 035 import org.objectweb.asm.ClassReader; 036 import org.objectweb.asm.ClassVisitor; 037 import org.objectweb.asm.ClassWriter; 038 import org.objectweb.asm.commons.Remapper; 039 import org.objectweb.asm.commons.RemappingClassAdapter; 040 import org.objectweb.asm.commons.SimpleRemapper; 041 042 import java.io.File; 043 import java.io.FileInputStream; 044 import java.io.FileOutputStream; 045 import java.io.IOException; 046 import java.io.InputStream; 047 import java.io.InputStreamReader; 048 import java.io.StringWriter; 049 import java.util.List; 050 import java.util.Properties; 051 052 /** 053 * Generates an <code>HelpMojo</code> class. 054 * 055 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> 056 * @version $Id: PluginHelpGenerator.java 1354250 2012-06-26 21:36:53Z hboutemy $ 057 * @since 2.4 058 */ 059 public class PluginHelpGenerator 060 extends AbstractLogEnabled 061 implements Generator 062 { 063 /** 064 * Default generated class name 065 */ 066 private static final String HELP_MOJO_CLASS_NAME = "HelpMojo"; 067 068 /** 069 * Help properties file, to store data about generated source. 070 */ 071 private static final String HELP_PROPERTIES_FILENAME = "maven-plugin-help.properties"; 072 073 /** 074 * Default goal 075 */ 076 private static final String HELP_GOAL = "help"; 077 078 private String helpPackageName; 079 080 private VelocityComponent velocityComponent; 081 082 /** 083 * Default constructor 084 */ 085 public PluginHelpGenerator() 086 { 087 this.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "PluginHelpGenerator" ) ); 088 } 089 090 // ---------------------------------------------------------------------- 091 // Public methods 092 // ---------------------------------------------------------------------- 093 094 /** 095 * {@inheritDoc} 096 */ 097 public void execute( File destinationDirectory, PluginToolsRequest request ) 098 throws GeneratorException 099 { 100 PluginDescriptor pluginDescriptor = request.getPluginDescriptor(); 101 102 String helpImplementation = getImplementation( pluginDescriptor ); 103 104 @SuppressWarnings( "unchecked" ) 105 List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos(); 106 107 if ( mojoDescriptors != null ) 108 { 109 // Verify that no help goal already exists 110 MojoDescriptor descriptor = pluginDescriptor.getMojo( HELP_GOAL ); 111 112 if ( ( descriptor != null ) && !descriptor.getImplementation().equals( helpImplementation ) ) 113 { 114 if ( getLogger().isWarnEnabled() ) 115 { 116 getLogger().warn( "\n\nA help goal (" + descriptor.getImplementation() 117 + ") already exists in this plugin. SKIPPED THE " + helpImplementation 118 + " GENERATION.\n" ); 119 } 120 121 return; 122 } 123 } 124 125 writeHelpPropertiesFile( request ); 126 127 try 128 { 129 String sourcePath = helpImplementation.replace( '.', File.separatorChar ) + ".java"; 130 131 File helpClass = new File( destinationDirectory, sourcePath ); 132 helpClass.getParentFile().mkdirs(); 133 134 MavenProject mavenProject = request.getProject(); 135 String pluginResourcesPath = "META-INF/maven/" + mavenProject.getGroupId() + "/" + mavenProject.getArtifactId(); 136 137 String helpClassSources = getHelpClassSources( pluginResourcesPath, pluginDescriptor ); 138 139 FileUtils.fileWrite( helpClass, request.getEncoding(), helpClassSources ); 140 } 141 catch ( IOException e ) 142 { 143 throw new GeneratorException( e.getMessage(), e ); 144 } 145 } 146 147 public PluginHelpGenerator setHelpPackageName( String helpPackageName ) 148 { 149 this.helpPackageName = helpPackageName; 150 return this; 151 } 152 153 public VelocityComponent getVelocityComponent() 154 { 155 return velocityComponent; 156 } 157 158 public PluginHelpGenerator setVelocityComponent( VelocityComponent velocityComponent ) 159 { 160 this.velocityComponent = velocityComponent; 161 return this; 162 } 163 164 // ---------------------------------------------------------------------- 165 // Private methods 166 // ---------------------------------------------------------------------- 167 168 private String getHelpClassSources( String pluginResourcesPath, PluginDescriptor pluginDescriptor ) 169 { 170 Properties properties = new Properties(); 171 VelocityContext context = new VelocityContext( properties ); 172 if ( this.helpPackageName != null ) 173 { 174 properties.put( "helpPackageName", this.helpPackageName ); 175 } 176 else 177 { 178 properties.put( "helpPackageName", "" ); 179 } 180 properties.put( "pluginHelpPath", pluginResourcesPath + "/plugin-help.xml" ); 181 properties.put( "artifactId", pluginDescriptor.getArtifactId() ); 182 properties.put( "goalPrefix", pluginDescriptor.getGoalPrefix() ); 183 184 // FIXME encoding ! 185 186 StringWriter stringWriter = new StringWriter(); 187 188 InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( "help-class-source.vm" ); 189 InputStreamReader isReader = new InputStreamReader( is ); 190 velocityComponent.getEngine().evaluate( context, stringWriter, "", isReader ); 191 192 return stringWriter.toString(); 193 } 194 195 /** 196 * @param pluginDescriptor The descriptor of the plugin for which to generate a help goal, must not be 197 * <code>null</code>. 198 * @return The implementation. 199 */ 200 private String getImplementation( PluginDescriptor pluginDescriptor ) 201 { 202 String packageName = helpPackageName; 203 if ( StringUtils.isEmpty( packageName ) ) 204 { 205 packageName = GeneratorUtils.discoverPackageName( pluginDescriptor ); 206 } 207 208 return StringUtils.isEmpty( packageName ) ? HELP_MOJO_CLASS_NAME : packageName + '.' + HELP_MOJO_CLASS_NAME; 209 } 210 211 /** 212 * Write help properties files for later use to eventually rewrite Help Mojo. 213 * 214 * @param request 215 * @throws GeneratorException 216 * @see {@link #rewriteHelpMojo(PluginToolsRequest)} 217 */ 218 private void writeHelpPropertiesFile( PluginToolsRequest request ) 219 throws GeneratorException 220 { 221 Properties properties = new Properties(); 222 properties.put( "helpPackageName", helpPackageName == null ? "" : helpPackageName ); 223 224 File tmpPropertiesFile = 225 new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME ); 226 227 if ( tmpPropertiesFile.exists() ) 228 { 229 tmpPropertiesFile.delete(); 230 } 231 else if ( !tmpPropertiesFile.getParentFile().exists() ) 232 { 233 tmpPropertiesFile.getParentFile().mkdirs(); 234 } 235 236 FileOutputStream fos = null; 237 try 238 { 239 fos = new FileOutputStream( tmpPropertiesFile ); 240 properties.store( fos, "maven plugin help mojo generation informations" ); 241 } 242 catch ( IOException e ) 243 { 244 throw new GeneratorException( e.getMessage(), e ); 245 } 246 finally 247 { 248 IOUtil.close( fos ); 249 } 250 } 251 252 /** 253 * Rewrite Help Mojo to match actual Mojos package name if it was not available at source generation 254 * time. This is used at descriptor generation time. 255 * 256 * @param request 257 * @throws GeneratorException 258 */ 259 static void rewriteHelpMojo( PluginToolsRequest request ) 260 throws GeneratorException 261 { 262 File tmpPropertiesFile = 263 new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME ); 264 265 if ( !tmpPropertiesFile.exists() ) 266 { 267 return; 268 } 269 270 Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile ); 271 272 String helpPackageName = properties.getProperty( "helpPackageName" ); 273 274 // if helpPackageName property is empty, we have to rewrite the class with a better package name than empty 275 if ( StringUtils.isEmpty( helpPackageName ) ) 276 { 277 String helpMojoImplementation = rewriteHelpClassToMojoPackage( request ); 278 279 if ( helpMojoImplementation != null ) 280 { 281 // rewrite plugin descriptor with new HelpMojo implementation class 282 updateHelpMojoDescriptor( request.getPluginDescriptor(), helpMojoImplementation ); 283 } 284 } 285 } 286 287 private static String rewriteHelpClassToMojoPackage( PluginToolsRequest request ) 288 throws GeneratorException 289 { 290 String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() ); 291 if ( StringUtils.isEmpty( destinationPackage ) ) 292 { 293 return null; 294 } 295 String packageAsDirectory = StringUtils.replace( destinationPackage, '.', '/' ); 296 297 String outputDirectory = request.getProject().getBuild().getOutputDirectory(); 298 File helpClassFile = new File( outputDirectory, HELP_MOJO_CLASS_NAME + ".class" ); 299 if ( !helpClassFile.exists() ) 300 { 301 return null; 302 } 303 304 File rewriteHelpClassFile = 305 new File( outputDirectory + '/' + packageAsDirectory, HELP_MOJO_CLASS_NAME + ".class" ); 306 if ( !rewriteHelpClassFile.getParentFile().exists() ) 307 { 308 rewriteHelpClassFile.getParentFile().mkdirs(); 309 } 310 311 FileInputStream fileInputStream = null; 312 ClassReader cr = null; 313 try 314 { 315 fileInputStream = new FileInputStream( helpClassFile ); 316 cr = new ClassReader( fileInputStream ); 317 } 318 catch ( IOException e ) 319 { 320 throw new GeneratorException( e.getMessage(), e ); 321 } 322 finally 323 { 324 IOUtil.close( fileInputStream ); 325 } 326 327 ClassWriter cw = new ClassWriter( 0 ); 328 329 Remapper packageRemapper = 330 new SimpleRemapper( HELP_MOJO_CLASS_NAME, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME ); 331 ClassVisitor cv = new RemappingClassAdapter( cw, packageRemapper ); 332 333 try 334 { 335 cr.accept( cv, ClassReader.EXPAND_FRAMES ); 336 } 337 catch ( Throwable e ) 338 { 339 throw new GeneratorException( "ASM issue processing class-file " + helpClassFile.getPath(), e ); 340 } 341 342 byte[] renamedClass = cw.toByteArray(); 343 FileOutputStream fos = null; 344 try 345 { 346 fos = new FileOutputStream( rewriteHelpClassFile ); 347 fos.write( renamedClass ); 348 } 349 catch ( IOException e ) 350 { 351 throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e ); 352 } 353 finally 354 { 355 IOUtil.close( fos ); 356 } 357 358 helpClassFile.delete(); 359 360 return destinationPackage + ".HelpMojo"; 361 } 362 363 private static void updateHelpMojoDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation ) 364 { 365 MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( HELP_GOAL ); 366 367 if ( mojoDescriptor != null ) 368 { 369 mojoDescriptor.setImplementation( helpMojoImplementation ); 370 } 371 } 372 }