diff --git a/parent/pom.xml b/parent/pom.xml index 09c6fb70a..7c5e761a1 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -119,7 +119,7 @@ junit junit - 4.11 + 4.12 diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java index af1df415e..720485bfd 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java @@ -23,6 +23,7 @@ import java.net.URL; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; @@ -45,6 +46,9 @@ import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; */ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader(); + private final Class klass; + private Class klassToUse; + private ReplacableTestClass replacableTestClass; /** * @param klass the test class @@ -52,7 +56,8 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { * @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)} */ public AnnotationProcessorTestRunner(Class klass) throws Exception { - super( replaceClassLoaderAndClass( klass ) ); + super( klass ); + this.klass = klass; } /** @@ -63,17 +68,10 @@ public class AnnotationProcessorTestRunner extends BlockJUnit4ClassRunner { * @return the class loaded with the test class loader */ private static Class replaceClassLoaderAndClass(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() ); - - TEST_CLASS_LOADER.addURL( basePath ); - - Thread.currentThread().setContextClassLoader( TEST_CLASS_LOADER ); + replaceContextClassLoader( klass ); try { - return TEST_CLASS_LOADER.loadClass( klass.getName() ); + return Thread.currentThread().getContextClassLoader().loadClass( klass.getName() ); } catch ( ClassNotFoundException e ) { throw new RuntimeException( e ); @@ -81,10 +79,48 @@ public class AnnotationProcessorTestRunner 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 ); + + Thread.currentThread().setContextClassLoader( testClassLoader ); + } + + @Override + protected TestClass createTestClass(final Class testClass) { + replacableTestClass = new ReplacableTestClass( testClass ); + return replacableTestClass; + } + + private FrameworkMethod replaceFrameworkMethod(FrameworkMethod m) { + try { + return new FrameworkMethod( + klassToUse.getDeclaredMethod( m.getName(), m.getMethod().getParameterTypes() ) ); + } + catch ( NoSuchMethodException e ) { + throw new RuntimeException( e ); + } + } + @Override protected Statement methodBlock(FrameworkMethod method) { - Statement statement = super.methodBlock( method ); - statement = new CompilingStatement( statement, method, TEST_CLASS_LOADER ); + CompilingStatement statement = new CompilingStatement( method ); + if ( statement.needsRecompilation() ) { + klassToUse = replaceClassLoaderAndClass( klass ); + + replacableTestClass.replaceClass( klassToUse ); + } + + method = replaceFrameworkMethod( method ); + + Statement next = super.methodBlock( method ); + + statement.setNextStatement( next ); return statement; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java index c70fcd0a1..45c630331 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java @@ -30,6 +30,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; + import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; @@ -94,20 +95,24 @@ class CompilingStatement extends Statement { "joda-time.jar" ); - private final Statement next; + private Statement next; private final FrameworkMethod method; - private final ModifiableURLClassLoader classloader; private JavaCompiler compiler; private String sourceDir; private String classOutputDir; private String sourceOutputDir; private List classPath; + private CompilationRequest compilationRequest; - public CompilingStatement(Statement next, FrameworkMethod method, ModifiableURLClassLoader classloader) { - this.next = next; + public CompilingStatement(FrameworkMethod method) { this.method = method; - this.classloader = classloader; + + this.compilationRequest = new CompilationRequest( getTestClasses(), getProcessorOptions() ); + } + + public void setNextStatement(Statement next) { + this.next = next; } @Override @@ -141,11 +146,11 @@ class CompilingStatement extends Statement { createOutputDirs(); - classloader.addOutputDir( classOutputDir ); + ( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).addOutputDir( classOutputDir ); } protected void generateMapperImplementation() throws Exception { - CompilationResultHolder compilationResult = compile( getTestClasses(), getProcessorOptions() ); + CompilationResultHolder compilationResult = compile(); CompilationOutcomeDescriptor actualResult = CompilationOutcomeDescriptor.forResult( @@ -285,12 +290,11 @@ class CompilingStatement extends Statement { return sourceFiles; } - private CompilationResultHolder compile(Set> sourceClasses, List processorOptions) + private CompilationResultHolder compile() throws Exception { - CompilationRequest request = new CompilationRequest( sourceClasses, processorOptions ); CompilationCache cache = COMPILATION_CACHE.get(); - if ( request.equals( cache.lastRequest ) ) { + if ( !needsRecompilation() ) { return cache.lastResult; } @@ -301,7 +305,7 @@ class CompilingStatement extends Statement { StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); Iterable compilationUnits = - fileManager.getJavaFileObjectsFromFiles( getSourceFiles( sourceClasses ) ); + fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.sourceClasses ) ); try { fileManager.setLocation( StandardLocation.CLASS_PATH, classPath ); @@ -313,16 +317,27 @@ class CompilingStatement extends Statement { } CompilationTask task = - compiler.getTask( null, fileManager, diagnostics, processorOptions, null, compilationUnits ); + compiler.getTask( + null, + fileManager, + diagnostics, + compilationRequest.processorOptions, + null, + compilationUnits ); + task.setProcessors( Arrays.asList( new MappingProcessor() ) ); CompilationResultHolder resultHolder = new CompilationResultHolder( diagnostics, task.call() ); - cache.lastRequest = request; + cache.lastRequest = compilationRequest; cache.lastResult = resultHolder; return resultHolder; } + public boolean needsRecompilation() { + return !compilationRequest.equals( COMPILATION_CACHE.get().lastRequest ); + } + private String getBasePath() { try { return new File( "." ).getCanonicalPath(); diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java index 002ae646e..375e8ae49 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java @@ -36,6 +36,9 @@ public 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( + ORG_MAPSTRUCT_AP_TEST ); + static { tryRegisterAsParallelCapable(); } @@ -43,7 +46,7 @@ public class ModifiableURLClassLoader extends URLClassLoader { private final ConcurrentMap addedURLs = new ConcurrentHashMap(); public ModifiableURLClassLoader() { - super( new URL[] { }, new FilteringParentClassLoader() ); + super( new URL[] { }, PARENT_CLASS_LOADER ); } @Override @@ -96,9 +99,15 @@ public class ModifiableURLClassLoader extends URLClassLoader { } private static final class FilteringParentClassLoader extends ClassLoader { + private String excludedPackage; + + public FilteringParentClassLoader(String excludedPackage) { + this.excludedPackage = excludedPackage; + } + @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if ( name.startsWith( ORG_MAPSTRUCT_AP_TEST ) ) { + if ( name.startsWith( excludedPackage ) ) { return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ReplacableTestClass.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ReplacableTestClass.java new file mode 100644 index 000000000..37deb9697 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ReplacableTestClass.java @@ -0,0 +1,212 @@ +/** + * Copyright 2012-2014 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.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.util.List; + +import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.TestClass; + +/** + * A {@link TestClass} where the wrapped Class can be replaced + * + * @author Andreas Gudian + */ +class ReplacableTestClass extends TestClass { + private TestClass delegate; + + /** + * @see TestClass#TestClass(Class) + */ + ReplacableTestClass(Class clazz) { + super( clazz ); + } + + /** + * @param clazz the new class + */ + void replaceClass(Class clazz) { + delegate = new TestClass( clazz ); + } + + @Override + public List getAnnotatedMethods() { + if ( null == delegate ) { + return super.getAnnotatedMethods(); + } + else { + return delegate.getAnnotatedMethods(); + } + } + + @Override + public List getAnnotatedMethods(Class annotationClass) { + if ( null == delegate ) { + return super.getAnnotatedMethods( annotationClass ); + } + else { + return delegate.getAnnotatedMethods( annotationClass ); + } + } + + @Override + public List getAnnotatedFields() { + if ( null == delegate ) { + return super.getAnnotatedFields(); + } + else { + return delegate.getAnnotatedFields(); + } + } + + @Override + public List getAnnotatedFields(Class annotationClass) { + if ( null == delegate ) { + return super.getAnnotatedFields( annotationClass ); + } + else { + return delegate.getAnnotatedFields( annotationClass ); + } + } + + @Override + public Class getJavaClass() { + if ( null == delegate ) { + return super.getJavaClass(); + } + else { + return delegate.getJavaClass(); + } + } + + @Override + public String getName() { + if ( null == delegate ) { + return super.getName(); + } + else { + return delegate.getName(); + } + } + + @Override + public Constructor getOnlyConstructor() { + if ( null == delegate ) { + return super.getOnlyConstructor(); + } + else { + return delegate.getOnlyConstructor(); + } + } + + @Override + public Annotation[] getAnnotations() { + if ( null == delegate ) { + return super.getAnnotations(); + } + else { + return delegate.getAnnotations(); + } + } + + @Override + public T getAnnotation(Class annotationType) { + if ( null == delegate ) { + return super.getAnnotation( annotationType ); + } + else { + return delegate.getAnnotation( annotationType ); + } + } + + @Override + public List getAnnotatedFieldValues(Object test, Class annotationClass, + Class valueClass) { + if ( null == delegate ) { + return super.getAnnotatedFieldValues( test, annotationClass, valueClass ); + } + else { + return delegate.getAnnotatedFieldValues( test, annotationClass, valueClass ); + } + } + + @Override + public List getAnnotatedMethodValues(Object test, Class annotationClass, + Class valueClass) { + if ( null == delegate ) { + return super.getAnnotatedMethodValues( test, annotationClass, valueClass ); + } + else { + return delegate.getAnnotatedMethodValues( test, annotationClass, valueClass ); + } + } + + @Override + public String toString() { + if ( null == delegate ) { + return super.toString(); + } + else { + return delegate.toString(); + } + } + + @Override + public boolean isPublic() { + if ( null == delegate ) { + return super.isPublic(); + } + else { + return delegate.isPublic(); + } + } + + @Override + public boolean isANonStaticInnerClass() { + if ( null == delegate ) { + return super.isANonStaticInnerClass(); + } + else { + return delegate.isANonStaticInnerClass(); + } + } + + @Override + public int hashCode() { + if ( null == delegate ) { + return super.hashCode(); + } + else { + return delegate.hashCode(); + } + } + + @Override + public boolean equals(Object obj) { + if ( null == delegate ) { + return super.equals( obj ); + } + else { + return delegate.equals( obj ); + } + } +}