package org.codehaus.mojo.apt;
/*
* The MIT License
*
* Copyright 2006-2008 The Codehaus.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
import org.codehaus.plexus.util.StringUtils;
/**
* Base mojo for executing apt.
*
* @author <a href="mailto:jubu@codehaus.org">Juraj Burian</a>
* @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
* @version $Id$
*/
public abstract class AbstractAptMojo extends AbstractMojo
{
// read-only parameters ---------------------------------------------------
/**
* The maven project.
*
* @parameter default-value="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* The plugin's artifacts.
*
* @parameter default-value="${plugin.artifacts}"
* @required
* @readonly
*/
private List<Artifact> pluginArtifacts;
/**
* The directory to run apt from when forked.
*
* @parameter default-value="${basedir}"
* @required
* @readonly
*/
private File workingDirectory;
// configurable parameters ------------------------------------------------
/**
* Whether to run apt in a separate process.
*
* @parameter default-value="false"
*/
private boolean fork;
/**
* The apt executable to use when forked.
*
* @parameter expression="${maven.apt.executable}" default-value="apt"
*/
private String executable;
/**
* The initial size of the memory allocation pool when forked, for example <code>64m</code>.
*
* @parameter
*/
private String meminitial;
/**
* The maximum size of the memory allocation pool when forked, for example <code>128m</code>.
*
* @parameter
*/
private String maxmem;
/**
* Whether to show apt warnings. This is opposite to the <code>-nowarn</code> argument for apt.
*
* @parameter expression="${maven.apt.showWarnings}" default-value="false"
*/
private boolean showWarnings;
/**
* The source file encoding name, such as <code>EUC-JP</code> and <code>UTF-8</code>. If encoding is not
* specified, the encoding <code>ISO-8859-1</code> is used rather than the platform default for reproducibility
* reasons. This is equivalent to the <code>-encoding</code> argument for apt.
*
* @parameter expression="${maven.apt.encoding}" default-value="ISO-8859-1"
*/
private String encoding;
/**
* Whether to output information about each class loaded and each source file processed. This is equivalent to the
* <code>-verbose</code> argument for apt.
*
* @parameter expression="${maven.apt.verbose}" default-value="false"
*/
private boolean verbose;
/**
* Options to pass to annotation processors. These are equivalent to multiple <code>-A</code> arguments for apt.
*
* @parameter
*/
private String[] options;
/**
* Name of <code>AnnotationProcessorFactory</code> to use; bypasses default discovery process. This is equivalent
* to the <code>-factory</code> argument for apt.
*
* @parameter expression="${maven.apt.factory}"
*/
private String factory;
/**
* The source directories containing any additional sources to be processed.
*
* @parameter
*/
private List<String> additionalSourceRoots;
/**
* A set of inclusion filters for apt. Default value is <code>**/*.java</code>.
*
* @parameter
*/
private Set<String> includes;
/**
* A set of exclusion filters for apt.
*
* @parameter
*/
private Set<String> excludes;
/**
* The path for processor-generated resources.
*
* @parameter
*/
private String resourceTargetPath;
/**
* Whether resource filtering is enabled for processor-generated resources.
*
* @parameter default-value="false"
*/
private boolean resourceFiltering;
/**
* Force apt processing without staleness checking. When <code>false</code>, use <code>outputFiles</code> or
* <code>outputFileEndings</code> to control computing staleness.
*
* @parameter default-value="false"
*/
private boolean force;
/**
* The filenames of processor-generated files to examine when computing staleness. For example,
* <code>generated.xml</code> would specify that the processor creates the aforementioned single file from all
* <code>.java</code> source files. When this parameter is not specified, <code>outputFileEndings</code> is used
* instead.
*
* @parameter
*/
private Set<String> outputFiles;
/**
* The filename endings of processor-generated files to examine when computing staleness. For example,
* <code>.txt</code> would specify that the processor creates a corresponding <code>.txt</code> file for every
* <code>.java</code> source file. Default value is <code>.java</code>. Note that this parameter has no effect if
* <code>outputFiles</code> is specified.
*
* @parameter
*/
private Set<String> outputFileEndings;
/**
* Sets the granularity in milliseconds of the last modification date for testing whether a source needs
* processing.
*
* @parameter expression="${maven.apt.staleMillis}" default-value="0"
*/
private int staleMillis;
/**
* Whether to bypass running apt.
*
* @parameter expression="${maven.apt.skip}" default-value="false"
*/
private boolean skip;
// fields -----------------------------------------------------------------
private Set<String> effectiveIncludes;
private Set<String> effectiveExcludes;
// Mojo methods -----------------------------------------------------------
/**
* {@inheritDoc}
*/
public final void execute() throws MojoExecutionException
{
if ( skip )
{
getLog().info( "Skipping apt" );
}
else
{
executeImpl();
}
}
// protected methods ------------------------------------------------------
protected void executeImpl() throws MojoExecutionException
{
// apply defaults
effectiveIncludes = CollectionUtils.defaultSet( includes, Collections.singleton( "**/*.java" ) );
effectiveExcludes = CollectionUtils.defaultSet( excludes );
// invoke apt
List<File> staleSourceFiles = getSourceFiles( getStaleScanner(), "stale sources" );
if ( staleSourceFiles.isEmpty() )
{
getLog().info( "Nothing to process - all processor-generated files are up to date" );
}
else
{
executeApt();
}
// add source root
String sourcePath = getSourceOutputDirectory().getPath();
if ( !getCompileSourceRoots().contains( sourcePath ) )
{
getCompileSourceRoots().add( sourcePath );
}
// add resource
String resourcePath = getOutputDirectory().getPath();
if ( !MavenProjectUtils.containsDirectory( getResources(), resourcePath ) )
{
Resource resource = new Resource();
resource.setDirectory( resourcePath );
resource.addExclude( "**/*.java" );
resource.setFiltering( resourceFiltering );
if ( resourceTargetPath != null )
{
resource.setTargetPath( resourceTargetPath );
}
getResources().add( resource );
}
}
/**
* Gets the Maven project.
*
* @return the project
*/
protected MavenProject getProject()
{
return project;
}
/**
* Gets the options to pass to annotation processors.
*
* @return an array of options to pass to annotation processors
*/
protected String[] getOptions()
{
return options;
}
/**
* The source directories containing the sources to be processed.
*
* @return list of compilation source roots
*/
protected abstract List<String> getCompileSourceRoots();
/**
* Gets the project's resources.
*
* @return the project's resources
*/
protected abstract List<Resource> getResources();
/**
* Gets the project's classpath.
*
* @return a list of classpath elements
*/
protected abstract List<String> getClasspathElements();
/**
* The directory to place processor and generated class files.
*
* @return the directory to place processor and generated class files
*/
protected abstract File getOutputDirectory();
/**
* The directory root under which processor-generated source files will be placed; files are placed in
* subdirectories based on package namespace.
*
* @return the directory root under which processor-generated source files will be placed
*/
protected abstract File getSourceOutputDirectory();
// private methods --------------------------------------------------------
private void executeApt() throws MojoExecutionException
{
List<File> sourceFiles = getSourceFiles( getSourceScanner(), "sources" );
if ( getLog().isInfoEnabled() )
{
int count = sourceFiles.size();
getLog().info( "Processing " + count + " source file" + ( count == 1 ? "" : "s" ) );
}
List<String> args = createArgs( sourceFiles );
boolean success;
if ( fork )
{
success = AptUtils.invokeForked( getLog(), workingDirectory, executable, meminitial, maxmem, args );
}
else
{
success = AptUtils.invoke( getLog(), args );
}
if ( !success )
{
throw new MojoExecutionException( "Apt failed" );
}
}
private List<String> createArgs( List<File> sourceFiles ) throws MojoExecutionException
{
List<String> args = new ArrayList<String>();
// javac arguments
Set<String> classpathElements = new LinkedHashSet<String>();
classpathElements.addAll( getPluginClasspathElements() );
classpathElements.addAll( getClasspathElements() );
if ( !classpathElements.isEmpty() )
{
args.add( "-classpath" );
args.add( toPath( classpathElements ) );
}
List<String> sourcePaths = getSourcePaths();
if ( !sourcePaths.isEmpty() )
{
args.add( "-sourcepath" );
args.add( toPath( sourcePaths ) );
}
args.add( "-d" );
args.add( getOutputDirectory().getAbsolutePath() );
if ( !showWarnings )
{
args.add( "-nowarn" );
}
if ( encoding != null )
{
args.add( "-encoding" );
args.add( encoding );
}
if ( verbose )
{
args.add( "-verbose" );
}
// apt arguments
args.add( "-s" );
args.add( getSourceOutputDirectory().getAbsolutePath() );
// never compile
args.add( "-nocompile" );
if ( options != null )
{
for ( String option : options )
{
args.add( "-A" + option.trim() );
}
}
if ( StringUtils.isNotEmpty( factory ) )
{
args.add( "-factory" );
args.add( factory );
}
// source files
for ( File file : sourceFiles )
{
args.add( file.getAbsolutePath() );
}
return args;
}
private static String toPath( Collection<String> paths )
{
StringBuffer buffer = new StringBuffer();
for ( Iterator<String> iterator = paths.iterator(); iterator.hasNext(); )
{
buffer.append( iterator.next() );
if ( iterator.hasNext() )
{
buffer.append( File.pathSeparator );
}
}
return buffer.toString();
}
private List<String> getPluginClasspathElements() throws MojoExecutionException
{
try
{
return MavenProjectUtils.getClasspathElements( project, pluginArtifacts );
}
catch ( DependencyResolutionRequiredException exception )
{
throw new MojoExecutionException( "Cannot get plugin classpath elements", exception );
}
}
private List<String> getSourcePaths()
{
List<String> sourcePaths = new ArrayList<String>();
sourcePaths.addAll( getCompileSourceRoots() );
if ( additionalSourceRoots != null )
{
sourcePaths.addAll( additionalSourceRoots );
}
return sourcePaths;
}
private List<File> getSourceFiles( SourceInclusionScanner scanner, String name ) throws MojoExecutionException
{
List<File> sourceFiles = new ArrayList<File>();
for ( String path : getSourcePaths() )
{
File sourceDir = new File( path );
sourceFiles.addAll( getSourceFiles( scanner, name, sourceDir ) );
}
return sourceFiles;
}
private Set<File> getSourceFiles( SourceInclusionScanner scanner, String name, File sourceDir )
throws MojoExecutionException
{
Set<File> sources;
if ( sourceDir.isDirectory() )
{
try
{
Set<?> rawSources = scanner.getIncludedSources( sourceDir, getOutputDirectory() );
sources = CollectionUtils.genericSet( rawSources, File.class );
}
catch ( InclusionScanException exception )
{
throw new MojoExecutionException( "Error scanning source directory: " + sourceDir, exception );
}
}
else
{
sources = Collections.emptySet();
}
if ( getLog().isDebugEnabled() )
{
if ( sources.isEmpty() )
{
getLog().debug( "No " + name + " found in " + sourceDir );
}
else
{
getLog().debug( StringUtils.capitalizeFirstLetter( name ) + " found in " + sourceDir + ":" );
LogUtils.log( getLog(), LogUtils.LEVEL_DEBUG, sources, " " );
}
}
return sources;
}
private SourceInclusionScanner getStaleScanner()
{
// create scanner
SourceInclusionScanner scanner;
if ( force )
{
if ( !CollectionUtils.isEmpty( outputFiles ) || !CollectionUtils.isEmpty( outputFileEndings ) )
{
getLog().warn( "Not using staleness checking - ignoring outputFiles and outputFileEndings" );
}
getLog().debug( "Processing all source files" );
scanner = createSimpleScanner();
}
else
{
scanner = new StaleSourceScanner( staleMillis, effectiveIncludes, effectiveExcludes );
if ( !CollectionUtils.isEmpty( outputFiles ) )
{
if ( !CollectionUtils.isEmpty( outputFileEndings ) )
{
getLog().warn( "Both outputFiles and outputFileEndings specified - using outputFiles" );
}
getLog().debug( "Computing stale sources against target files " + outputFiles );
for ( String file : outputFiles )
{
scanner.addSourceMapping( new SingleTargetSourceMapping( ".java", file ) );
}
}
else
{
Set<String> suffixes = CollectionUtils.defaultSet( outputFileEndings, Collections.singleton( ".java" ) );
getLog().debug( "Computing stale sources against target file endings " + suffixes );
scanner.addSourceMapping( new SuffixMapping( ".java", suffixes ) );
}
}
return scanner;
}
private SourceInclusionScanner getSourceScanner()
{
SourceInclusionScanner scanner;
if ( force || CollectionUtils.isEmpty( outputFiles ) )
{
scanner = getStaleScanner();
}
else
{
scanner = createSimpleScanner();
}
return scanner;
}
private SourceInclusionScanner createSimpleScanner()
{
SourceInclusionScanner scanner = new SimpleSourceInclusionScanner( effectiveIncludes, effectiveExcludes );
// dummy mapping required to function
scanner.addSourceMapping( new SuffixMapping( "", "" ) );
return scanner;
}
}