package org.hibernate.jpamodelgen.test.util;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.fail;
/**
* A custom JUnit statement which will run annotation processor prior to execute the original statement/test.
*
* The classes to process are specified via {@code WithClasses}, {@code WithMappingFiles} and {@code WithProcessorOption}
* on the actual test.
*
* @author Hardy Ferentschik
*/
public class CompilationStatement extends Statement {
private static final Logger log = LoggerFactory.getLogger( CompilationStatement.class );
private static final String PACKAGE_SEPARATOR = ".";
private static final String ANNOTATION_PROCESSOR_OPTION_PREFIX = "-A";
private static final String SOURCE_BASE_DIR_PROPERTY = "sourceBaseDir";
private static final String SOURCE_BASE_DIR;
static {
// first we try to guess the target directory. This will work, if the build is triggered from the
// command line or the output directory used by the id is within the project directory (eg 'out' in Intellij).
File targetDir = TestUtil.getTargetDir();
File potentialSourceDirectory = new File( targetDir.getParent(), "src/test/java" );
if ( potentialSourceDirectory.exists() ) {
SOURCE_BASE_DIR = potentialSourceDirectory.getAbsolutePath();
}
else {
String tmp = System.getProperty( SOURCE_BASE_DIR_PROPERTY );
if ( tmp == null ) {
fail(
"Unable to guess determine the source directory. Specify the system property 'sourceBaseDir'" +
" pointing to the base directory of the test java sources."
);
}
SOURCE_BASE_DIR = tmp;
}
}
private final Statement originalStatement;
private final List<Class<?>> testEntities;
private final List<Class<?>> preCompileEntities;
private final List<String> xmlMappingFiles;
private final Map<String, String> processorOptions;
private final boolean ignoreCompilationErrors;
private final List<Diagnostic<?>> compilationDiagnostics;
public CompilationStatement(Statement originalStatement,
List<Class<?>> testEntities,
List<Class<?>> proCompileEntities,
List<String> xmlMappingFiles,
Map<String, String> processorOptions,
boolean ignoreCompilationErrors) {
this.originalStatement = originalStatement;
this.testEntities = testEntities;
this.preCompileEntities = proCompileEntities;
this.xmlMappingFiles = xmlMappingFiles;
this.processorOptions = processorOptions;
this.ignoreCompilationErrors = ignoreCompilationErrors;
this.compilationDiagnostics = new ArrayList<Diagnostic<?>>();
}
@Override
public void evaluate() throws Throwable {
try {
// some test needs to compile some classes prior to the actual classes under test
if ( !preCompileEntities.isEmpty() ) {
compile( getCompilationUnits( preCompileEntities ) );
}
// now we compile the actual test classes
compile( getCompilationUnits( testEntities ) );
if ( !ignoreCompilationErrors ) {
TestUtil.assertNoCompilationError( compilationDiagnostics );
}
}
catch ( Exception e ) {
StringWriter errors = new StringWriter();
e.printStackTrace( new PrintWriter( errors ) );
log.debug( errors.toString() );
fail( "Unable to process test sources." );
}
originalStatement.evaluate();
}
private List<File> getCompilationUnits(List<Class<?>> classesToCompile) {
List<File> javaFiles = new ArrayList<File>();
for ( Class<?> testClass : classesToCompile ) {
String pathToSource = getPathToSource( testClass );
javaFiles.add( new File( pathToSource ) );
}
return javaFiles;
}
private String getPathToSource(Class<?> testClass) {
return SOURCE_BASE_DIR + File.separator + testClass.getName()
.replace( PACKAGE_SEPARATOR, File.separator ) + ".java";
}
private void compile(List<File> sourceFiles) throws Exception {
List<String> options = createJavaOptions();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager( diagnostics, null, null );
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(
sourceFiles
);
compileSources( options, compiler, diagnostics, fileManager, compilationUnits );
compilationDiagnostics.addAll( diagnostics.getDiagnostics() );
fileManager.close();
}
private List<String> createJavaOptions() {
List<String> options = new ArrayList<String>();
options.add( "-d" );
options.add( TestUtil.getOutBaseDir().getAbsolutePath() );
// pass orm files if specified
if ( !xmlMappingFiles.isEmpty() ) {
StringBuilder builder = new StringBuilder();
builder.append( ANNOTATION_PROCESSOR_OPTION_PREFIX );
builder.append( JPAMetaModelEntityProcessor.ORM_XML_OPTION );
builder.append( "=" );
for ( String ormFile : xmlMappingFiles ) {
builder.append( ormFile );
builder.append( "," );
}
builder.deleteCharAt( builder.length() - 1 );
options.add( builder.toString() );
}
// add any additional options specified by the test
for ( Map.Entry<String, String> entry : processorOptions.entrySet() ) {
StringBuilder builder = new StringBuilder();
builder.append( ANNOTATION_PROCESSOR_OPTION_PREFIX );
builder.append( entry.getKey() );
builder.append( "=" );
builder.append( entry.getValue() );
options.add( builder.toString() );
}
return options;
}
private void compileSources(List<String> options,
JavaCompiler compiler,
DiagnosticCollector<JavaFileObject> diagnostics,
StandardJavaFileManager fileManager,
Iterable<? extends JavaFileObject> compilationUnits) {
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, options, null, compilationUnits
);
task.call();
for ( Diagnostic<?> diagnostic : diagnostics.getDiagnostics() ) {
log.debug( diagnostic.getMessage( null ) );
}
}
}