blob: 27688cfa7e255454c4d8a5716858f3dbc8ae09f0 [file] [log] [blame]
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.ClassRemapper;
import org.objectweb.asm.commons.Remapper;
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 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>
* @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 )
throws IOException
{
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();
// plugin-tools sources are UTF-8 (and even ASCII in this case))
try ( InputStream is = //
Thread.currentThread().getContextClassLoader().getResourceAsStream( "help-class-source.vm" ); //
InputStreamReader isReader = new InputStreamReader( is, "UTF-8" ) )
{
//isReader =
velocityComponent.getEngine().evaluate( context, stringWriter, "", isReader );
}
catch ( UnsupportedEncodingException e )
{
// not supposed to happen since UTF-8 is supposed to be supported by any JVM
}
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();
}
try ( FileOutputStream fos = new FileOutputStream( tmpPropertiesFile ) )
{
properties.store( fos, "maven plugin help mojo generation informations" );
}
catch ( IOException e )
{
throw new GeneratorException( e.getMessage(), e );
}
}
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;
try
{
properties = PropertyUtils.loadProperties( tmpPropertiesFile );
}
catch ( IOException e )
{
throw new GeneratorException( e.getMessage(), e );
}
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();
}
try ( Reader sourceReader = new InputStreamReader( new FileInputStream( helpSourceFile ), //
request.getEncoding() ); //
PrintWriter 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 );
}
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();
}
ClassReader cr;
try ( FileInputStream fileInputStream = new FileInputStream( helpClassFile ) )
{
cr = new ClassReader( fileInputStream );
}
catch ( IOException e )
{
throw new GeneratorException( e.getMessage(), e );
}
ClassWriter cw = new ClassWriter( 0 );
Remapper packageRemapper =
new SimpleRemapper( HELP_MOJO_CLASS_NAME, packageAsDirectory + '/' + HELP_MOJO_CLASS_NAME );
ClassVisitor cv = new ClassRemapper( 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();
try ( FileOutputStream fos = new FileOutputStream( rewriteHelpClassFile ) )
{
fos.write( renamedClass );
}
catch ( IOException e )
{
throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e );
}
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 );
}
}
}