/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.ap.testutil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import org.hibernate.validator.ap.util.CollectionHelper;
import org.hibernate.validator.ap.util.Configuration;
import org.hibernate.validator.ap.util.DiagnosticExpectation;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
/**
* Infrastructure for unit tests based on the Java Compiler API.
*
* @author Gunnar Morling
*/
public class CompilerTestHelper {
/**
* Dependencies which are used as class path elements for the compilation tasks.
*
* @author Gunnar Morling
*/
public enum Library {
HIBERNATE_VALIDATOR( "hibernate-validator.jar" ),
VALIDATION_API( "validation-api.jar" ),
JODA_TIME( "joda-time.jar" ),
JAVA_MONEY_API( "money-api.jar" );
private final String name;
private Library(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
private final JavaCompiler compiler;
private final String sourceBaseDir;
private final String testLibraryDir;
private static final File BASE_DIR;
private static final File TARGET_DIR;
private static final File PROCESSOR_OUT_DIR;
static {
TARGET_DIR = getTargetDir();
BASE_DIR = TARGET_DIR.getParentFile();
PROCESSOR_OUT_DIR = new File( TARGET_DIR, "processor-generated-test-classes" );
if ( !PROCESSOR_OUT_DIR.exists() ) {
if ( !PROCESSOR_OUT_DIR.mkdirs() ) {
fail( "Unable to create test output directory " + PROCESSOR_OUT_DIR.toString() );
}
}
}
public CompilerTestHelper(JavaCompiler compiler) {
this.compiler = compiler;
this.sourceBaseDir = BASE_DIR.getAbsolutePath() + "/src/test/java";
this.testLibraryDir = TARGET_DIR.getAbsolutePath() + "/test-dependencies";
}
/**
* Retrieves a file object containing the source of the given class.
*
* @param clazz The class of interest.
*
* @return A file with the source of the given class.
*/
public File getSourceFile(Class<?> clazz) {
String sourceFileName = File.separator + clazz.getName().replace( ".", File.separator ) + ".java";
return new File( sourceBaseDir + sourceFileName );
}
/**
* @see CompilerTestHelper#compile(Processor, DiagnosticCollector, Kind, Boolean, Boolean, EnumSet, File...)
*/
public boolean compile(Processor annotationProcessor,
DiagnosticCollector<JavaFileObject> diagnostics,
File... sourceFiles) {
return compile(
annotationProcessor,
diagnostics,
null,
null,
null,
EnumSet.allOf( Library.class ),
sourceFiles
);
}
/**
* @see CompilerTestHelper#compile(Processor, DiagnosticCollector, Kind, Boolean, Boolean, EnumSet, File...)
*/
public boolean compile(Processor annotationProcessor,
DiagnosticCollector<JavaFileObject> diagnostics,
boolean verbose,
boolean allowMethodConstraints,
File... sourceFiles) {
return compile(
annotationProcessor,
diagnostics,
null,
verbose,
allowMethodConstraints,
EnumSet.allOf( Library.class ),
sourceFiles
);
}
/**
* @see CompilerTestHelper#compile(Processor, DiagnosticCollector, Kind, Boolean, Boolean, EnumSet, File...)
*/
public boolean compile(Processor annotationProcessor,
DiagnosticCollector<JavaFileObject> diagnostics,
EnumSet<Library> dependencies,
File... sourceFiles) {
return compile( annotationProcessor, diagnostics, null, null, null, dependencies, sourceFiles );
}
/**
* Creates and executes a {@link CompilationTask} using the given input.
*
* @param annotationProcessor An annotation processor to be attached to the task.
* @param diagnostics An diagnostics listener to be attached to the task.
* @param diagnosticKind A value for the "diagnosticKind" option.
* @param verbose A value for the "verbose" option.
* @param allowMethodConstraints A value for the "methodConstraintsSupported" option.
* @param dependencies A set with libraries which shall be added to the class path of
* the compilation task.
* @param sourceFiles The source files to be compiled.
*
* @return True, if the source files could be compiled successfully (meaning
* in especially, that the given annotation processor didn't raise
* any errors), false otherwise.
*/
public boolean compile(Processor annotationProcessor,
DiagnosticCollector<JavaFileObject> diagnostics,
Kind diagnosticKind,
Boolean verbose,
Boolean allowMethodConstraints,
EnumSet<Library> dependencies,
File... sourceFiles) {
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects( sourceFiles );
List<String> options = new ArrayList<String>();
if ( diagnosticKind != null ) {
options.add( String.format( "-A%s=%s", Configuration.DIAGNOSTIC_KIND_PROCESSOR_OPTION, diagnosticKind ) );
}
if ( verbose != null ) {
options.add( String.format( "-A%s=%b", Configuration.VERBOSE_PROCESSOR_OPTION, verbose ) );
}
if ( allowMethodConstraints != null ) {
options.add(
String.format(
"-A%s=%b",
Configuration.METHOD_CONSTRAINTS_SUPPORTED_PROCESSOR_OPTION,
allowMethodConstraints
)
);
}
try {
fileManager.setLocation( StandardLocation.CLASS_PATH, getDependenciesAsFiles( dependencies ) );
fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( PROCESSOR_OUT_DIR ) );
}
catch (IOException e) {
throw new RuntimeException( e );
}
CompilationTask task = compiler.getTask( null, fileManager, diagnostics, options, null, compilationUnits );
task.setProcessors( Arrays.asList( annotationProcessor ) );
return task.call();
}
/**
* <p>
* Asserts, that the given diagnostics match with the given expectations.
* </p>
* <p>
* First checks, whether the number of actual diagnostics matches with the
* number of given expectations. If that's the case, {@link Kind} and line
* number of each expectation are compared.
* </p>
*
* @param diagnostics The actual diagnostics as populated by the executed
* {@link CompilationTask}.
* @param expectations The expectations to compare against.
*/
public static void assertThatDiagnosticsMatch(DiagnosticCollector<JavaFileObject> diagnostics, DiagnosticExpectation... expectations) {
assertEquals( asExpectations( diagnostics.getDiagnostics() ), CollectionHelper.asTreeSet( expectations ) );
}
private static Set<DiagnosticExpectation> asExpectations(Collection<Diagnostic<? extends JavaFileObject>> diagnosticsList) {
Set<DiagnosticExpectation> theValue = CollectionHelper.newTreeSet();
for ( Diagnostic<? extends JavaFileObject> diagnostic : diagnosticsList ) {
theValue.add( new DiagnosticExpectation( diagnostic.getKind(), diagnostic.getLineNumber() ) );
}
return theValue;
}
private Set<File> getDependenciesAsFiles(EnumSet<Library> dependencies) {
Set<File> files = new HashSet<File>();
for ( Library oneDependency : dependencies ) {
files.add( new File( testLibraryDir + File.separator + oneDependency.getName() ) );
}
return files;
}
/**
* Returns the target directory of the build.
*
* @return the target directory of the build
*/
public static File getTargetDir() {
// target/test-classes
String targetClassesDir = CompilerTestHelper.class.getProtectionDomain().getCodeSource().getLocation().getFile();
return new File( targetClassesDir ).getParentFile();
}
}