| package org.apache.maven.tools.plugin.generator; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.maven.plugin.descriptor.MojoDescriptor; |
| import org.apache.maven.plugin.descriptor.PluginDescriptor; |
| import org.apache.maven.plugin.logging.Log; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.tools.plugin.PluginToolsRequest; |
| import org.apache.velocity.VelocityContext; |
| import org.codehaus.plexus.logging.AbstractLogEnabled; |
| import org.codehaus.plexus.logging.Logger; |
| import org.codehaus.plexus.logging.console.ConsoleLogger; |
| import org.codehaus.plexus.util.FileUtils; |
| import org.codehaus.plexus.util.IOUtil; |
| import org.codehaus.plexus.util.PropertyUtils; |
| import org.codehaus.plexus.util.StringUtils; |
| import org.codehaus.plexus.velocity.VelocityComponent; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.commons.Remapper; |
| import org.objectweb.asm.commons.RemappingClassAdapter; |
| import org.objectweb.asm.commons.SimpleRemapper; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.io.Reader; |
| import java.io.StringWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.util.List; |
| import java.util.Properties; |
| |
| /** |
| * Generates an <code>HelpMojo</code> class from <code>help-class-source.vm</code> template. |
| * The generated mojo reads help content from <code>META-INF/maven/${groupId}/${artifactId}/plugin-help.xml</code> |
| * resource, which is generated by this {@link PluginDescriptorGenerator}. |
| * <p>Notice that the help mojo source needs to be generated before compilation, but when Java 5 annotations are used, |
| * plugin descriptor content is available only after compilation (detecting annotations in .class files): |
| * help mojo source can be generated with empty package only (and no plugin descriptor available yet), then needs |
| * to be updated after compilation - through {@link #rewriteHelpMojo(PluginToolsRequest, Log)} which is called from |
| * plugin descriptor XML generation.</p> |
| * |
| * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> |
| * @version $Id$ |
| * @since 2.4 |
| */ |
| public class PluginHelpGenerator |
| extends AbstractLogEnabled |
| implements Generator |
| { |
| /** |
| * Default generated class name |
| */ |
| private static final String HELP_MOJO_CLASS_NAME = "HelpMojo"; |
| |
| /** |
| * Help properties file, to store data about generated source. |
| */ |
| private static final String HELP_PROPERTIES_FILENAME = "maven-plugin-help.properties"; |
| |
| /** |
| * Default goal |
| */ |
| private static final String HELP_GOAL = "help"; |
| |
| private String helpPackageName; |
| |
| private boolean useAnnotations; |
| |
| private VelocityComponent velocityComponent; |
| |
| /** |
| * Default constructor |
| */ |
| public PluginHelpGenerator() |
| { |
| this.enableLogging( new ConsoleLogger( Logger.LEVEL_INFO, "PluginHelpGenerator" ) ); |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Public methods |
| // ---------------------------------------------------------------------- |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void execute( File destinationDirectory, PluginToolsRequest request ) |
| throws GeneratorException |
| { |
| PluginDescriptor pluginDescriptor = request.getPluginDescriptor(); |
| |
| String helpImplementation = getImplementation( pluginDescriptor ); |
| |
| @SuppressWarnings( "unchecked" ) |
| List<MojoDescriptor> mojoDescriptors = pluginDescriptor.getMojos(); |
| |
| if ( mojoDescriptors != null ) |
| { |
| // Verify that no help goal already exists |
| MojoDescriptor descriptor = pluginDescriptor.getMojo( HELP_GOAL ); |
| |
| if ( ( descriptor != null ) && !descriptor.getImplementation().equals( helpImplementation ) ) |
| { |
| if ( getLogger().isWarnEnabled() ) |
| { |
| getLogger().warn( "\n\nA help goal (" + descriptor.getImplementation() |
| + ") already exists in this plugin. SKIPPED THE " + helpImplementation |
| + " GENERATION.\n" ); |
| } |
| |
| return; |
| } |
| } |
| |
| writeHelpPropertiesFile( request, destinationDirectory ); |
| |
| useAnnotations = |
| request.getProject().getArtifactMap().containsKey( |
| "org.apache.maven.plugin-tools:maven-plugin-annotations" ); |
| |
| try |
| { |
| String sourcePath = helpImplementation.replace( '.', File.separatorChar ) + ".java"; |
| |
| File helpClass = new File( destinationDirectory, sourcePath ); |
| helpClass.getParentFile().mkdirs(); |
| |
| String helpClassSources = getHelpClassSources( getPluginHelpPath( request.getProject() ), |
| pluginDescriptor ); |
| |
| FileUtils.fileWrite( helpClass, request.getEncoding(), helpClassSources ); |
| } |
| catch ( IOException e ) |
| { |
| throw new GeneratorException( e.getMessage(), e ); |
| } |
| } |
| |
| public PluginHelpGenerator setHelpPackageName( String helpPackageName ) |
| { |
| this.helpPackageName = helpPackageName; |
| return this; |
| } |
| |
| public VelocityComponent getVelocityComponent() |
| { |
| return velocityComponent; |
| } |
| |
| public PluginHelpGenerator setVelocityComponent( VelocityComponent velocityComponent ) |
| { |
| this.velocityComponent = velocityComponent; |
| return this; |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Private methods |
| // ---------------------------------------------------------------------- |
| |
| private String getHelpClassSources( String pluginHelpPath, PluginDescriptor pluginDescriptor ) |
| { |
| Properties properties = new Properties(); |
| VelocityContext context = new VelocityContext( properties ); |
| if ( this.helpPackageName != null ) |
| { |
| properties.put( "helpPackageName", this.helpPackageName ); |
| } |
| else |
| { |
| properties.put( "helpPackageName", "" ); |
| } |
| properties.put( "pluginHelpPath", pluginHelpPath ); |
| properties.put( "artifactId", pluginDescriptor.getArtifactId() ); |
| properties.put( "goalPrefix", pluginDescriptor.getGoalPrefix() ); |
| properties.put( "useAnnotations", useAnnotations ); |
| |
| StringWriter stringWriter = new StringWriter(); |
| |
| InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream( "help-class-source.vm" ); |
| InputStreamReader isReader = null; |
| try |
| { |
| isReader = new InputStreamReader( is, "UTF-8" ); // plugin-tools sources are UTF-8 (and even ASCII in this |
| // case) |
| velocityComponent.getEngine().evaluate( context, stringWriter, "", isReader ); |
| } |
| catch ( UnsupportedEncodingException e ) |
| { |
| // not supposed to happen since UTF-8 is supposed to be supported by any JVM |
| } |
| finally |
| { |
| IOUtil.close( is ); |
| IOUtil.close( isReader ); |
| } |
| |
| return stringWriter.toString(); |
| } |
| |
| /** |
| * @param pluginDescriptor The descriptor of the plugin for which to generate a help goal, must not be |
| * <code>null</code>. |
| * @return The implementation. |
| */ |
| private String getImplementation( PluginDescriptor pluginDescriptor ) |
| { |
| if ( StringUtils.isEmpty( helpPackageName ) ) |
| { |
| helpPackageName = GeneratorUtils.discoverPackageName( pluginDescriptor ); |
| } |
| |
| return StringUtils.isEmpty( helpPackageName ) ? HELP_MOJO_CLASS_NAME : helpPackageName + '.' |
| + HELP_MOJO_CLASS_NAME; |
| } |
| |
| /** |
| * Write help properties files for later use to eventually rewrite Help Mojo. |
| * |
| * @param request |
| * @throws GeneratorException |
| * @see {@link #rewriteHelpMojo(PluginToolsRequest, Log)} |
| */ |
| private void writeHelpPropertiesFile( PluginToolsRequest request, File destinationDirectory ) |
| throws GeneratorException |
| { |
| Properties properties = new Properties(); |
| properties.put( "helpPackageName", helpPackageName == null ? "" : helpPackageName ); |
| properties.put( "destinationDirectory", destinationDirectory.getAbsolutePath() ); |
| |
| File tmpPropertiesFile = |
| new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME ); |
| |
| if ( tmpPropertiesFile.exists() ) |
| { |
| tmpPropertiesFile.delete(); |
| } |
| else if ( !tmpPropertiesFile.getParentFile().exists() ) |
| { |
| tmpPropertiesFile.getParentFile().mkdirs(); |
| } |
| |
| FileOutputStream fos = null; |
| try |
| { |
| fos = new FileOutputStream( tmpPropertiesFile ); |
| properties.store( fos, "maven plugin help mojo generation informations" ); |
| } |
| catch ( IOException e ) |
| { |
| throw new GeneratorException( e.getMessage(), e ); |
| } |
| finally |
| { |
| IOUtil.close( fos ); |
| } |
| } |
| |
| static String getPluginHelpPath( MavenProject mavenProject ) |
| { |
| return "META-INF/maven/" + mavenProject.getGroupId() + "/" + mavenProject.getArtifactId() + "/plugin-help.xml"; |
| } |
| |
| /** |
| * Rewrite Help Mojo to match actual Mojos package name if it was not available at source generation |
| * time. This is used at descriptor generation time. |
| * |
| * @param request |
| * @throws GeneratorException |
| */ |
| static void rewriteHelpMojo( PluginToolsRequest request, Log log ) |
| throws GeneratorException |
| { |
| File tmpPropertiesFile = |
| new File( request.getProject().getBuild().getDirectory(), HELP_PROPERTIES_FILENAME ); |
| |
| if ( !tmpPropertiesFile.exists() ) |
| { |
| return; |
| } |
| |
| Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile ); |
| |
| String helpPackageName = properties.getProperty( "helpPackageName" ); |
| |
| // if helpPackageName property is empty, we have to rewrite the class with a better package name than empty |
| if ( StringUtils.isEmpty( helpPackageName ) ) |
| { |
| String destDir = properties.getProperty( "destinationDirectory" ); |
| File destinationDirectory; |
| if ( StringUtils.isEmpty( destDir ) ) |
| { |
| // writeHelpPropertiesFile() creates 2 properties: find one without the other should not be possible |
| log.warn( "\n\nUnexpected situation: destinationDirectory not defined in " + HELP_PROPERTIES_FILENAME |
| + " during help mojo source generation but expected during XML descriptor generation." ); |
| log.warn( "Please check helpmojo goal version used in previous build phase." ); |
| log.warn( "If you just upgraded to plugin-tools >= 3.2 you must run a clean build at least once." ); |
| destinationDirectory = new File( "target/generated-sources/plugin" ); |
| log.warn( "Trying default location: " + destinationDirectory ); |
| } |
| else |
| { |
| destinationDirectory = new File( destDir ); |
| } |
| String helpMojoImplementation = rewriteHelpClassToMojoPackage( request, destinationDirectory, log ); |
| |
| if ( helpMojoImplementation != null ) |
| { |
| // rewrite plugin descriptor with new HelpMojo implementation class |
| updateHelpMojoDescriptor( request.getPluginDescriptor(), helpMojoImplementation ); |
| } |
| } |
| } |
| |
| private static String rewriteHelpClassToMojoPackage( PluginToolsRequest request, File destinationDirectory, |
| Log log ) |
| throws GeneratorException |
| { |
| String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() ); |
| if ( StringUtils.isEmpty( destinationPackage ) ) |
| { |
| return null; |
| } |
| String packageAsDirectory = StringUtils.replace( destinationPackage, '.', '/' ); |
| |
| String outputDirectory = request.getProject().getBuild().getOutputDirectory(); |
| File helpClassFile = new File( outputDirectory, HELP_MOJO_CLASS_NAME + ".class" ); |
| if ( !helpClassFile.exists() ) |
| { |
| return null; |
| } |
| |
| // rewrite help mojo source |
| File helpSourceFile = new File( destinationDirectory, HELP_MOJO_CLASS_NAME + ".java" ); |
| if ( !helpSourceFile.exists() ) |
| { |
| log.warn( "HelpMojo.java not found in default location: " + helpSourceFile.getAbsolutePath() ); |
| log.warn( "Help goal source won't be moved to package: " + destinationPackage ); |
| } |
| else |
| { |
| File helpSourceFileNew = |
| new File( destinationDirectory, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME + ".java" ); |
| if ( !helpSourceFileNew.getParentFile().exists() ) |
| { |
| helpSourceFileNew.getParentFile().mkdirs(); |
| } |
| Reader sourceReader = null; |
| PrintWriter sourceWriter = null; |
| try |
| { |
| sourceReader = new InputStreamReader( new FileInputStream( helpSourceFile ), request.getEncoding() ); |
| sourceWriter = |
| new PrintWriter( new OutputStreamWriter( new FileOutputStream( helpSourceFileNew ), |
| request.getEncoding() ) ); |
| |
| sourceWriter.println( "package " + destinationPackage + ";" ); |
| IOUtil.copy( sourceReader, sourceWriter ); |
| } |
| catch ( IOException e ) |
| { |
| throw new GeneratorException( e.getMessage(), e ); |
| } |
| finally |
| { |
| IOUtil.close( sourceReader ); |
| IOUtil.close( sourceWriter ); |
| } |
| helpSourceFileNew.setLastModified( helpSourceFile.lastModified() ); |
| helpSourceFile.delete(); |
| } |
| |
| // rewrite help mojo .class |
| File rewriteHelpClassFile = |
| new File( outputDirectory + '/' + packageAsDirectory, HELP_MOJO_CLASS_NAME + ".class" ); |
| if ( !rewriteHelpClassFile.getParentFile().exists() ) |
| { |
| rewriteHelpClassFile.getParentFile().mkdirs(); |
| } |
| |
| FileInputStream fileInputStream = null; |
| ClassReader cr = null; |
| try |
| { |
| fileInputStream = new FileInputStream( helpClassFile ); |
| cr = new ClassReader( fileInputStream ); |
| } |
| catch ( IOException e ) |
| { |
| throw new GeneratorException( e.getMessage(), e ); |
| } |
| finally |
| { |
| IOUtil.close( fileInputStream ); |
| } |
| |
| ClassWriter cw = new ClassWriter( 0 ); |
| |
| Remapper packageRemapper = |
| new SimpleRemapper( HELP_MOJO_CLASS_NAME, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME ); |
| ClassVisitor cv = new RemappingClassAdapter( cw, packageRemapper ); |
| |
| try |
| { |
| cr.accept( cv, ClassReader.EXPAND_FRAMES ); |
| } |
| catch ( Throwable e ) |
| { |
| throw new GeneratorException( "ASM issue processing class-file " + helpClassFile.getPath(), e ); |
| } |
| |
| byte[] renamedClass = cw.toByteArray(); |
| FileOutputStream fos = null; |
| try |
| { |
| fos = new FileOutputStream( rewriteHelpClassFile ); |
| fos.write( renamedClass ); |
| } |
| catch ( IOException e ) |
| { |
| throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e ); |
| } |
| finally |
| { |
| IOUtil.close( fos ); |
| } |
| |
| helpClassFile.delete(); |
| |
| return destinationPackage + ".HelpMojo"; |
| } |
| |
| private static void updateHelpMojoDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation ) |
| { |
| MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( HELP_GOAL ); |
| |
| if ( mojoDescriptor != null ) |
| { |
| mojoDescriptor.setImplementation( helpMojoImplementation ); |
| } |
| } |
| } |