#825 Add @WithServiceImplementation annotation in testutil to register SPI implementations in processor tests.

This commit is contained in:
Andreas Gudian 2016-07-03 14:15:33 +02:00
parent 1e6b81176d
commit 051177e409
15 changed files with 735 additions and 87 deletions

View File

@ -51,6 +51,7 @@
<exclude>**/*Erroneous*.java</exclude>
<exclude>**/*Test.java</exclude>
<exclude>**/testutil/**/*.java</exclude>
<exclude>**/spi/**/*.java</exclude>
<exclude>${additionalExclude0}</exclude>
<exclude>${additionalExclude1}</exclude>
<exclude>${additionalExclude2}</exclude>

View File

@ -0,0 +1,84 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.test.naming.spi;
import java.beans.Introspector;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.MethodType;
/**
* A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the
* form of {@code withProperty(value)}.
*
* @author Gunnar Morling
*/
public class CustomAccessorNamingStrategy implements AccessorNamingStrategy {
@Override
public MethodType getMethodType(ExecutableElement method) {
if ( isGetterMethod( method ) ) {
return MethodType.GETTER;
}
else if ( isSetterMethod( method ) ) {
return MethodType.SETTER;
}
else if ( isAdderMethod( method ) ) {
return MethodType.ADDER;
}
else {
return MethodType.OTHER;
}
}
private boolean isGetterMethod(ExecutableElement method) {
return method.getReturnType().getKind() != TypeKind.VOID;
}
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "with" ) && methodName.length() > 4;
}
public boolean isAdderMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "add" ) && methodName.length() > 3;
}
@Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return Introspector.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName );
}
@Override
public String getElementName(ExecutableElement adderMethod) {
String methodName = adderMethod.getSimpleName().toString();
return Introspector.decapitalize( methodName.substring( 3 ) );
}
@Override
public String getCollectionGetterName(String property) {
return property.substring( 0, 1 ).toUpperCase() + property.substring( 1 );
}
}

View File

@ -0,0 +1,51 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.test.naming.spi;
import static org.fest.assertions.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithServiceImplementation;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
/**
* Test do demonstrate the usage of custom implementations of {@link AccessorNamingStrategy}.
*
* @author Andreas Gudian
*/
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses({ GolfPlayer.class, GolfPlayerDto.class, GolfPlayerMapper.class })
@WithServiceImplementation(CustomAccessorNamingStrategy.class)
public class CustomNamingStrategyTest {
@Test
public void shouldApplyCustomNamingStrategy() {
GolfPlayer player = new GolfPlayer()
.withName( "Jared" )
.withHandicap( 9.2D );
GolfPlayerDto dto = GolfPlayerMapper.INSTANCE.golfPlayerToDto( player );
assertThat( dto ).isNotNull();
assertThat( dto.name() ).isEqualTo( "Jared" );
assertThat( dto.handicap() ).isEqualTo( 9.2D );
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.test.naming.spi;
/**
* @author Gunnar Morling
*/
public class GolfPlayer {
private double handicap;
private String name;
public double handicap() {
return handicap;
}
public GolfPlayer withHandicap(double handicap) {
this.handicap = handicap;
return this;
}
public String name() {
return name;
}
public GolfPlayer withName(String name) {
this.name = name;
return this;
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.test.naming.spi;
/**
* @author Gunnar Morling
*/
public class GolfPlayerDto {
private double handicap;
private String name;
public double handicap() {
return handicap;
}
public void withHandicap(double handicap) {
this.handicap = handicap;
}
public String name() {
return name;
}
public void withName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,30 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.test.naming.spi;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface GolfPlayerMapper {
GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );
GolfPlayerDto golfPlayerToDto(GolfPlayer player);
}

View File

@ -0,0 +1,44 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.testutil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies an implementation of an SPI to be used during the annotation processing.
*
* @author Andreas Gudian
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface WithServiceImplementation {
/**
* @return The service implementation class that is to be made available during the annotation processing.
*/
Class<?> value();
/**
* @return The SPI that the service implementation provides. If omitted, then {@link #value()} is expected to
* implement exactly one interface which will be considered the provided SPI implementation.
*/
Class<?> provides() default Object.class;
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.testutil;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies implementations of SPIs to be used during the annotation processing.
*
* @author Andreas Gudian
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface WithServiceImplementations {
/**
* @return The SPI implementations to use.
*/
WithServiceImplementation[] value();
}

View File

@ -19,17 +19,20 @@
package org.mapstruct.ap.testutil.runner;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a compilation task for a number of sources with given processor options.
*/
class CompilationRequest {
public class CompilationRequest {
private final Set<Class<?>> sourceClasses;
private final Map<Class<?>, Class<?>> services;
private final List<String> processorOptions;
CompilationRequest(Set<Class<?>> sourceClasses, List<String> processorOptions) {
CompilationRequest(Set<Class<?>> sourceClasses, Map<Class<?>, Class<?>> services, List<String> processorOptions) {
this.sourceClasses = sourceClasses;
this.services = services;
this.processorOptions = processorOptions;
}
@ -38,6 +41,7 @@ class CompilationRequest {
final int prime = 31;
int result = 1;
result = prime * result + ( ( processorOptions == null ) ? 0 : processorOptions.hashCode() );
result = prime * result + ( ( services == null ) ? 0 : services.hashCode() );
result = prime * result + ( ( sourceClasses == null ) ? 0 : sourceClasses.hashCode() );
return result;
}
@ -55,7 +59,9 @@ class CompilationRequest {
}
CompilationRequest other = (CompilationRequest) obj;
return processorOptions.equals( other.processorOptions ) && sourceClasses.equals( other.sourceClasses );
return processorOptions.equals( other.processorOptions )
&& services.equals( other.services )
&& sourceClasses.equals( other.sourceClasses );
}
public Set<Class<?>> getSourceClasses() {
@ -65,4 +71,8 @@ class CompilationRequest {
public List<String> getProcessorOptions() {
return processorOptions;
}
public Map<Class<?>, Class<?>> getServices() {
return services;
}
}

View File

@ -22,21 +22,26 @@ import static org.fest.assertions.Assertions.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithServiceImplementation;
import org.mapstruct.ap.testutil.WithServiceImplementations;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
@ -67,7 +72,9 @@ abstract class CompilingStatement extends Statement {
protected static final String SOURCE_DIR = getBasePath() + "/src/test/java";
protected static final List<String> COMPILER_CLASSPATH = buildCompilerClasspath();
protected static final List<String> TEST_COMPILATION_CLASSPATH = buildTestCompilationClasspath();
protected static final List<String> PROCESSOR_CLASSPATH = buildProcessorClasspath();
private final FrameworkMethod method;
private final CompilationCache compilationCache;
@ -75,13 +82,14 @@ abstract class CompilingStatement extends Statement {
private String classOutputDir;
private String sourceOutputDir;
private String additionalCompilerClasspath;
private CompilationRequest compilationRequest;
CompilingStatement(FrameworkMethod method, CompilationCache compilationCache) {
this.method = method;
this.compilationCache = compilationCache;
this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() );
this.compilationRequest = new CompilationRequest( getTestClasses(), getServices(), getProcessorOptions() );
}
void setNextStatement(Statement next) {
@ -110,32 +118,48 @@ abstract class CompilingStatement extends Statement {
classOutputDir = compilationRoot + "/classes";
sourceOutputDir = compilationRoot + "/generated-sources";
additionalCompilerClasspath = compilationRoot + "/compiler";
createOutputDirs();
( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir );
( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).withPath( classOutputDir );
}
protected abstract String getPathSuffix();
private static List<String> buildCompilerClasspath() {
String[] bootClasspath =
System.getProperty( "java.class.path" ).split( System.getProperty( "path.separator" ) );
String fs = System.getProperty( "file.separator" );
String testClasses = "target" + fs + "test-classes";
private static List<String> buildTestCompilationClasspath() {
String[] whitelist =
new String[] {
"processor" + fs + "target", // the processor itself
"core" + fs + "target", // MapStruct annotations in multi-module reactor build or IDE
"org" + fs + "mapstruct" + fs + "mapstruct" + fs, // MapStruct annotations in single module build
"freemarker",
// MapStruct annotations in multi-module reactor build or IDE
"core" + File.separator + "target",
// MapStruct annotations in single module build
"org" + File.separator + "mapstruct" + File.separator + "mapstruct" + File.separator,
"guava",
"javax.inject",
"spring-beans",
"spring-context",
"joda-time" };
return filterBootClassPath( whitelist );
}
private static List<String> buildProcessorClasspath() {
String[] whitelist =
new String[] {
"processor" + File.separator + "target", // the processor itself,
"freemarker",
"javax.inject",
"spring-context",
"joda-time" };
return filterBootClassPath( whitelist );
}
protected static List<String> filterBootClassPath(String[] whitelist) {
String[] bootClasspath =
System.getProperty( "java.class.path" ).split( File.pathSeparator );
String testClasses = "target" + File.separator + "test-classes";
List<String> classpath = new ArrayList<String>();
for ( String path : bootClasspath ) {
if ( !path.contains( testClasses ) && isWhitelisted( path, whitelist ) ) {
@ -303,6 +327,55 @@ abstract class CompilingStatement extends Statement {
return testClasses;
}
/**
* Returns the resources to be compiled for this test.
*
* @return A map containing the package were to look for a resource (key) and the resource (value) to be compiled
* for this test
*/
private Map<Class<?>, Class<?>> getServices() {
Map<Class<?>, Class<?>> services = new HashMap<Class<?>, Class<?>>();
addServices( services, method.getAnnotation( WithServiceImplementations.class ) );
addService( services, method.getAnnotation( WithServiceImplementation.class ) );
Class<?> declaringClass = method.getMethod().getDeclaringClass();
addServices( services, declaringClass.getAnnotation( WithServiceImplementations.class ) );
addService( services, declaringClass.getAnnotation( WithServiceImplementation.class ) );
return services;
}
private void addServices(Map<Class<?>, Class<?>> services, WithServiceImplementations withImplementations) {
if ( withImplementations != null ) {
for ( WithServiceImplementation resource : withImplementations.value() ) {
addService( services, resource );
}
}
}
private void addService(Map<Class<?>, Class<?>> services, WithServiceImplementation annoation) {
if ( annoation == null ) {
return;
}
Class<?> provides = annoation.provides();
Class<?> implementor = annoation.value();
if ( provides == Object.class ) {
Class<?>[] implemented = implementor.getInterfaces();
if ( implemented.length != 1 ) {
throw new IllegalArgumentException(
"The class " + implementor.getName()
+ " either needs to implement exactly one interface, or \"provides\" needs to be specified"
+ " as well in the annotation " + WithServiceImplementation.class.getSimpleName() + "." );
}
provides = implemented[0];
}
services.put( provides, implementor );
}
/**
* Returns the processor options to be used this test.
*
@ -344,7 +417,7 @@ abstract class CompilingStatement extends Statement {
return String.format( "-A%s=%s", processorOption.name(), processorOption.value() );
}
protected Set<File> getSourceFiles(Collection<Class<?>> classes) {
protected static Set<File> getSourceFiles(Collection<Class<?>> classes) {
Set<File> sourceFiles = new HashSet<File>( classes.size() );
for ( Class<?> clazz : classes ) {
@ -369,17 +442,33 @@ abstract class CompilingStatement extends Statement {
setupDirectories();
compilationCache.setLastSourceOutputDir( sourceOutputDir );
CompilationOutcomeDescriptor resultHolder =
compileWithSpecificCompiler( compilationRequest, sourceOutputDir, classOutputDir );
boolean needsAdditionalCompilerClasspath = prepareServices();
CompilationOutcomeDescriptor resultHolder;
resultHolder = compileWithSpecificCompiler(
compilationRequest,
sourceOutputDir,
classOutputDir,
needsAdditionalCompilerClasspath ? additionalCompilerClasspath : null );
compilationCache.update( compilationRequest, resultHolder );
return resultHolder;
}
protected Object loadAndInstantiate(ClassLoader processorClassloader, Class<?> clazz) {
try {
return processorClassloader.loadClass( clazz.getName() ).newInstance();
}
catch ( Exception e ) {
throw new RuntimeException( e );
}
}
protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler(
CompilationRequest compilationRequest,
String sourceOutputDir,
String classOutputDir);
String classOutputDir,
String additionalCompilerClasspath);
boolean needsRecompilation() {
return !compilationRequest.equals( compilationCache.getLastRequest() );
@ -402,6 +491,10 @@ abstract class CompilingStatement extends Statement {
directory = new File( sourceOutputDir );
deleteDirectory( directory );
directory.mkdirs();
directory = new File( additionalCompilerClasspath );
deleteDirectory( directory );
directory.mkdirs();
}
private void deleteDirectory(File path) {
@ -419,6 +512,32 @@ abstract class CompilingStatement extends Statement {
path.delete();
}
private boolean prepareServices() {
if ( !compilationRequest.getServices().isEmpty() ) {
String servicesDir =
additionalCompilerClasspath + File.separator + "META-INF" + File.separator + "services";
File directory = new File( servicesDir );
deleteDirectory( directory );
directory.mkdirs();
for ( Map.Entry<Class<?>, Class<?>> serviceEntry : compilationRequest.getServices().entrySet() ) {
try {
File file = new File( servicesDir + File.separator + serviceEntry.getKey().getName() );
FileWriter fileWriter = new FileWriter( file );
fileWriter.append( serviceEntry.getValue().getName() ).append( "\n" );
fileWriter.flush();
fileWriter.close();
}
catch ( IOException e ) {
throw new RuntimeException( e );
}
}
return true;
}
return false;
}
private static class DiagnosticDescriptorComparator implements Comparator<DiagnosticDescriptor> {
@Override

View File

@ -19,6 +19,8 @@
package org.mapstruct.ap.testutil.runner;
import java.io.File;
import java.util.List;
import java.util.Set;
import org.codehaus.plexus.compiler.CompilerConfiguration;
import org.codehaus.plexus.compiler.CompilerException;
@ -36,6 +38,14 @@ import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor;
*/
class EclipseCompilingStatement extends CompilingStatement {
private static final List<String> ECLIPSE_COMPILER_CLASSPATH = buildEclipseCompilerClasspath();
private static final ClassLoader DEFAULT_ECLIPSE_COMPILER_CLASSLOADER =
new ModifiableURLClassLoader( newFilteringClassLoaderForEclipse() )
.withPaths( ECLIPSE_COMPILER_CLASSPATH )
.withPaths( PROCESSOR_CLASSPATH )
.withOriginOf( ClassLoaderExecutor.class );
EclipseCompilingStatement(FrameworkMethod method, CompilationCache compilationCache) {
super( method, compilationCache );
}
@ -43,36 +53,105 @@ class EclipseCompilingStatement extends CompilingStatement {
@Override
protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest,
String sourceOutputDir,
String classOutputDir) {
JDTCompiler compiler = new JDTCompiler();
compiler.enableLogging( new ConsoleLogger( 5, "JDT-Compiler" ) );
String classOutputDir,
String additionalCompilerClasspath) {
ClassLoader compilerClassloader;
if ( additionalCompilerClasspath == null ) {
compilerClassloader = DEFAULT_ECLIPSE_COMPILER_CLASSLOADER;
}
else {
ModifiableURLClassLoader loader = new ModifiableURLClassLoader(
newFilteringClassLoaderForEclipse()
.hidingClasses( compilationRequest.getServices().values() ) );
CompilerConfiguration config = new CompilerConfiguration();
config.setClasspathEntries( COMPILER_CLASSPATH );
config.setOutputLocation( classOutputDir );
config.setGeneratedSourcesDirectory( new File( sourceOutputDir ) );
config.setAnnotationProcessors( new String[] { MappingProcessor.class.getName() } );
config.setSourceFiles( getSourceFiles( compilationRequest.getSourceClasses() ) );
config.setShowWarnings( false );
config.setSourceVersion( "1.8" );
config.setTargetVersion( "1.8" );
for ( String option : compilationRequest.getProcessorOptions() ) {
config.addCompilerCustomArgument( option, null );
compilerClassloader = loader.withPaths( ECLIPSE_COMPILER_CLASSPATH )
.withPaths( PROCESSOR_CLASSPATH )
.withOriginOf( ClassLoaderExecutor.class )
.withPath( additionalCompilerClasspath )
.withOriginsOf( compilationRequest.getServices().values() );
}
CompilerResult compilerResult;
try {
compilerResult = compiler.performCompile( config );
}
catch ( CompilerException e ) {
throw new RuntimeException( e );
}
ClassLoaderHelper clHelper =
(ClassLoaderHelper) loadAndInstantiate( compilerClassloader, ClassLoaderExecutor.class );
return CompilationOutcomeDescriptor.forResult(
return clHelper.compileInOtherClassloader(
compilationRequest,
TEST_COMPILATION_CLASSPATH,
getSourceFiles( compilationRequest.getSourceClasses() ),
SOURCE_DIR,
compilerResult );
sourceOutputDir,
classOutputDir );
}
private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() {
return new FilteringParentClassLoader(
// reload eclipse compiler classes
"org.eclipse.",
// reload mapstruct processor classes
"org.mapstruct.ap.internal.",
"org.mapstruct.ap.spi.",
"org.mapstruct.ap.MappingProcessor")
.hidingClass( ClassLoaderExecutor.class );
}
public interface ClassLoaderHelper {
CompilationOutcomeDescriptor compileInOtherClassloader(CompilationRequest compilationRequest,
List<String> testCompilationClasspath,
Set<File> sourceFiles,
String sourceDir,
String sourceOutputDir,
String classOutputDir);
}
public static final class ClassLoaderExecutor implements ClassLoaderHelper {
@Override
public CompilationOutcomeDescriptor compileInOtherClassloader(CompilationRequest compilationRequest,
List<String> testCompilationClasspath,
Set<File> sourceFiles,
String sourceDir,
String sourceOutputDir,
String classOutputDir) {
JDTCompiler compiler = new JDTCompiler();
compiler.enableLogging( new ConsoleLogger( 5, "JDT-Compiler" ) );
CompilerConfiguration config = new CompilerConfiguration();
config.setClasspathEntries( testCompilationClasspath );
config.setOutputLocation( classOutputDir );
config.setGeneratedSourcesDirectory( new File( sourceOutputDir ) );
config.setAnnotationProcessors( new String[] { MappingProcessor.class.getName() } );
config.setSourceFiles( sourceFiles );
config.setShowWarnings( false );
config.setSourceVersion( "1.8" );
config.setTargetVersion( "1.8" );
for ( String option : compilationRequest.getProcessorOptions() ) {
config.addCompilerCustomArgument( option, null );
}
CompilerResult compilerResult;
try {
compilerResult = compiler.performCompile( config );
}
catch ( CompilerException e ) {
throw new RuntimeException( e );
}
return CompilationOutcomeDescriptor.forResult(
sourceDir,
compilerResult );
}
}
private static List<String> buildEclipseCompilerClasspath() {
String[] whitelist =
new String[] {
"tycho-compiler",
"org.eclipse.jdt.",
"plexus-compiler-api",
"plexus-component-annotations" };
return filterBootClassPath( whitelist );
}
@Override

View File

@ -0,0 +1,73 @@
/**
* Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed 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.
*/
package org.mapstruct.ap.testutil.runner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
/**
* A ClassLoader that can be used to hide packages or classes from its default parent ClassLoader so that the
* ClassLoader using this as its parent can load the class on its own.
*
* @author Andreas Gudian
*/
final class FilteringParentClassLoader extends ClassLoader {
private Collection<String> excludedPrefixes;
/**
* @param excludedPrefixes class name prefixes to exclude
*/
FilteringParentClassLoader(String... excludedPrefixes) {
this.excludedPrefixes = new ArrayList<String>( Arrays.asList( excludedPrefixes ) );
}
/**
* @param classes The classes to hide (inner classes are hidden as well)
* @return {@code this}
*/
FilteringParentClassLoader hidingClasses(Collection<Class<?>> classes) {
for ( Class<?> clazz : classes ) {
hidingClass( clazz );
}
return this;
}
/**
* @param clazz The class to hide (inner classes are hidden as well)
* @return {@code this}
*/
FilteringParentClassLoader hidingClass(Class<?> clazz) {
excludedPrefixes.add( clazz.getName() );
return this;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for ( String excluded : excludedPrefixes ) {
if ( name.startsWith( excluded ) ) {
return null;
}
}
return super.loadClass( name, resolve );
}
}

View File

@ -18,8 +18,6 @@
*/
package org.mapstruct.ap.testutil.runner;
import java.net.URL;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
@ -70,13 +68,7 @@ class InnerAnnotationProcessorRunner extends BlockJUnit4ClassRunner {
}
private static void replaceContextClassLoader(Class<?> klass) {
String classFileName = klass.getName().replace( ".", "/" ) + ".class";
URL classResource = klass.getClassLoader().getResource( classFileName );
String fullyQualifiedUrl = classResource.toExternalForm();
String basePath = fullyQualifiedUrl.substring( 0, fullyQualifiedUrl.length() - classFileName.length() );
ModifiableURLClassLoader testClassLoader = new ModifiableURLClassLoader();
testClassLoader.addURL( basePath );
ModifiableURLClassLoader testClassLoader = new ModifiableURLClassLoader().withOriginOf( klass );
Thread.currentThread().setContextClassLoader( testClassLoader );
}

View File

@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
@ -45,7 +46,11 @@ import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor;
*/
class JdkCompilingStatement extends CompilingStatement {
private static final List<File> COMPILER_CLASSPATH_FILES = asFiles( COMPILER_CLASSPATH );
private static final List<File> COMPILER_CLASSPATH_FILES = asFiles( TEST_COMPILATION_CLASSPATH );
private static final ClassLoader DEFAULT_PROCESSOR_CLASSLOADER =
new ModifiableURLClassLoader( new FilteringParentClassLoader( "org.mapstruct." ) )
.withPaths( PROCESSOR_CLASSPATH );
JdkCompilingStatement(FrameworkMethod method, CompilationCache compilationCache) {
super( method, compilationCache );
@ -54,7 +59,8 @@ class JdkCompilingStatement extends CompilingStatement {
@Override
protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest,
String sourceOutputDir,
String classOutputDir) {
String classOutputDir,
String additionalCompilerClasspath) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
@ -71,6 +77,18 @@ class JdkCompilingStatement extends CompilingStatement {
throw new RuntimeException( e );
}
ClassLoader processorClassloader;
if ( additionalCompilerClasspath == null ) {
processorClassloader = DEFAULT_PROCESSOR_CLASSLOADER;
}
else {
processorClassloader = new ModifiableURLClassLoader(
new FilteringParentClassLoader( "org.mapstruct." ) )
.withPaths( PROCESSOR_CLASSPATH )
.withPath( additionalCompilerClasspath )
.withOriginsOf( compilationRequest.getServices().values() );
}
CompilationTask task =
compiler.getTask(
null,
@ -80,9 +98,10 @@ class JdkCompilingStatement extends CompilingStatement {
null,
compilationUnits );
task.setProcessors( Arrays.asList( new MappingProcessor() ) );
task.setProcessors(
Arrays.asList( (Processor) loadAndInstantiate( processorClassloader, MappingProcessor.class ) ) );
Boolean compilationSuccessful = task.call();
boolean compilationSuccessful = task.call();
return CompilationOutcomeDescriptor.forResult(
SOURCE_DIR,

View File

@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -32,11 +33,11 @@ import java.util.concurrent.ConcurrentMap;
*
* @author Andreas Gudian
*/
public class ModifiableURLClassLoader extends URLClassLoader {
final class ModifiableURLClassLoader extends URLClassLoader {
private static final String ORG_MAPSTRUCT_AP_TEST = "org.mapstruct.ap.test.";
private static final FilteringParentClassLoader PARENT_CLASS_LOADER = new FilteringParentClassLoader(
private static final FilteringParentClassLoader DEFAULT_PARENT_CLASS_LOADER = new FilteringParentClassLoader(
ORG_MAPSTRUCT_AP_TEST );
static {
@ -45,8 +46,12 @@ public class ModifiableURLClassLoader extends URLClassLoader {
private final ConcurrentMap<URL, URL> addedURLs = new ConcurrentHashMap<URL, URL>();
public ModifiableURLClassLoader() {
super( new URL[] { }, PARENT_CLASS_LOADER );
ModifiableURLClassLoader(ClassLoader parent) {
super( new URL[] { }, parent );
}
ModifiableURLClassLoader() {
this( DEFAULT_PARENT_CLASS_LOADER );
}
@Override
@ -56,22 +61,51 @@ public class ModifiableURLClassLoader extends URLClassLoader {
}
}
public void addURL(String basePath) {
try {
addURL( new URL( basePath ) );
}
catch ( MalformedURLException e ) {
throw new RuntimeException( e );
ModifiableURLClassLoader withOriginsOf(Collection<Class<?>> classes) {
for ( Class<?> c : classes ) {
withOriginOf( c );
}
return this;
}
public void addOutputDir(String outputDir) {
ModifiableURLClassLoader withOriginOf(Class<?> clazz) {
String classFileName = clazz.getName().replace( ".", "/" ) + ".class";
URL classResource = clazz.getClassLoader().getResource( classFileName );
String fullyQualifiedUrl = classResource.toExternalForm();
String basePath = fullyQualifiedUrl.substring( 0, fullyQualifiedUrl.length() - classFileName.length() );
return withURL( basePath );
}
ModifiableURLClassLoader withPaths(Collection<String> baseUrls) {
for ( String url : baseUrls ) {
withPath( url );
}
return this;
}
ModifiableURLClassLoader withURL(String baseUrl) {
try {
addURL( new File( outputDir ).toURI().toURL() );
addURL( new URL( baseUrl ) );
}
catch ( MalformedURLException e ) {
throw new RuntimeException( e );
}
return this;
}
ModifiableURLClassLoader withPath(String path) {
try {
addURL( new File( path ).toURI().toURL() );
}
catch ( MalformedURLException e ) {
throw new RuntimeException( e );
}
return this;
}
private static void tryRegisterAsParallelCapable() {
@ -94,21 +128,4 @@ public class ModifiableURLClassLoader extends URLClassLoader {
return; // ignore
}
}
private static final class FilteringParentClassLoader extends ClassLoader {
private String excludedPackage;
FilteringParentClassLoader(String excludedPackage) {
this.excludedPackage = excludedPackage;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ( name.startsWith( excludedPackage ) ) {
return null;
}
return super.loadClass( name, resolve );
}
}
}