Suppose that we need to make some changes to our source code during a Maven build, e.g., we want to add a special method to a class for a testing Maven profile, but not for other Maven profiles. This post shows a method to achieve this by calling the Java Compiler API from a Maven plugin. The method does not require the knowledge of annotation processing or bytecode manipulation.
The idea is that we load the source files we want to modify into String
s, modify the String
s, and feed the String
s to the Java Compiler API to get the corresponding class files. The modification could be anything you want, e.g., replacing a special token with some value, as long as the String
s still compile.
We need to make a Maven plugin, which extracts properties, such as classpath, source directory, and output directory, from a Maven build, and passes to the Java Compiler API, as well as the modified String
s. We call the plugin SourceProcessingMojo
. We bind it to the process-classes
phase so that the classes generated by the Java Compiler API can overwrite classes with the same names generated in the compile
phase.
-
/**
-
* @goal source-processing
-
* @phase process-classes
-
* @requiresDependencyResolution compile
-
*/
-
public class SourceProcessingMojo extends AbstractMojo {
-
-
/**
-
* @required
-
* @readonly
-
* @parameter expression="${project.compileClasspathElements}"
-
*/
-
protected List<String> compileClasspathElements;
-
-
/**
-
* @required
-
* @parameter expression="${project.build.sourceDirectory}"
-
*/
-
protected String sourceDirectory;
-
-
/**
-
* @required
-
* @parameter expression="${project.build.outputDirectory}"
-
*/
-
protected String outputDirectory;
-
-
/**
-
* e.g.,
-
* me/shanhe/Foo;me/shanhe/Bar
-
* for
-
* me.shanhe.Foo.java and me.shanhe.Bar.java, respectively.
-
* @required
-
* @parameter expression="${filenames}"
-
*/
-
protected String filenames;
-
-
@Override
-
public void execute() throws MojoExecutionException, MojoFailureException {
-
final String classpath = StringUtility.implode(
-
File.pathSeparatorChar, compileClasspathElements);
-
final List<JavaFileObject> units = new LinkedList<JavaFileObject>();
-
for(String filename : StringUtility.explode(';', filenames)) {
-
units.add(new SourceProcessingJavaFileObject(sourceDirectory, filename));
-
}
-
final List<String> options = new LinkedList<String>();
-
options.add("-classpath");
-
options.add(classpath);
-
options.add("-d");
-
options.add(outputDirectory);
-
options.add("-encoding");
-
options.add("UTF-8");
-
options.add("-source");
-
options.add("1.6");
-
options.add("-target");
-
options.add("1.6");
-
options.add("-verbose");
-
if(false == ToolProvider
-
.getSystemJavaCompiler()
-
.getTask(null, null, null, options, null, units)
-
.call()
-
.booleanValue()) {
-
throw new MojoFailureException("source processing failure.");
-
}
-
}
-
}
The class SourceProcessingJavaFileObject
supplies the Java Compiler API with modified String
s.
-
public class SourceProcessingJavaFileObject extends SimpleJavaFileObject {
-
-
private final String filename;
-
-
public SourceProcessingJavaFileObject(
-
final String directory, final String filename) {
-
super(URI.create(String.format("string:///%s%s"
-
, filename, Kind.SOURCE.extension)), Kind.SOURCE);
-
this.filename = String.format("%s/%s%s"
-
, directory, filename, Kind.SOURCE.extension);
-
}
-
-
@Override
-
public CharSequence getCharContent(boolean unused) throws IOException {
-
// load the content of filename into a String.
-
// modify the String as needed.
-
// return the modified String.
-
}
-
}
In a Maven profile, we reference the plugin using a snippet like
-
<plugin>
-
<groupId>me.shanhe</groupId>
-
<artifactId>source-processing-mojo</artifactId>
-
<version>0.0.1-SNAPSHOT</version>
-
<executions>
-
<execution>
-
<id>source-processing</id>
-
<goals>
-
<goal>source-processing</goal>
-
</goals>
-
<configuration>
-
<filenames>me/shanhe/Foo;me/shanhe/Bar</filenames>
-
</configuration>
-
</execution>
-
</executions>
-
</plugin>
Assume that there are /src/main/java/me/shanhe/Foo.java
and /src/main/java/me/shanhe/Bar.java
.
Basically, we just called javac
through the Java Compiler API. You can also do annotation processing, static analysis, and more. Refer to http://docs.oracle.com/javase/6/docs/api/javax/tools/package-summary.html.
Post new comment