@ -18,17 +18,22 @@ package org.springframework.boot.testsupport.classpath;
import java.io.File ;
import java.io.File ;
import java.lang.management.ManagementFactory ;
import java.lang.management.ManagementFactory ;
import java.lang.reflect.AnnotatedElement ;
import java.lang.reflect.Method ;
import java.net.URISyntaxException ;
import java.net.URISyntaxException ;
import java.net.URL ;
import java.net.URL ;
import java.net.URLClassLoader ;
import java.net.URLClassLoader ;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Arrays ;
import java.util.Collections ;
import java.util.Collection ;
import java.util.LinkedHashSet ;
import java.util.List ;
import java.util.List ;
import java.util.Map ;
import java.util.Map ;
import java.util.Set ;
import java.util.jar.Attributes ;
import java.util.jar.Attributes ;
import java.util.jar.JarFile ;
import java.util.jar.JarFile ;
import java.util.regex.Pattern ;
import java.util.regex.Pattern ;
import java.util.stream.Collectors ;
import java.util.stream.Stream ;
import java.util.stream.Stream ;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils ;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils ;
@ -52,6 +57,7 @@ import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations ;
import org.springframework.core.annotation.MergedAnnotations ;
import org.springframework.util.AntPathMatcher ;
import org.springframework.util.AntPathMatcher ;
import org.springframework.util.ConcurrentReferenceHashMap ;
import org.springframework.util.ConcurrentReferenceHashMap ;
import org.springframework.util.ObjectUtils ;
import org.springframework.util.StringUtils ;
import org.springframework.util.StringUtils ;
/ * *
/ * *
@ -62,7 +68,7 @@ import org.springframework.util.StringUtils;
* /
* /
final class ModifiedClassPathClassLoader extends URLClassLoader {
final class ModifiedClassPathClassLoader extends URLClassLoader {
private static final Map < Class< ? > , ModifiedClassPathClassLoader > cache = new ConcurrentReferenceHashMap < > ( ) ;
private static final Map < List< AnnotatedElement > , ModifiedClassPathClassLoader > cache = new ConcurrentReferenceHashMap < > ( ) ;
private static final Pattern INTELLIJ_CLASSPATH_JAR_PATTERN = Pattern . compile ( ".*classpath(\\d+)?\\.jar" ) ;
private static final Pattern INTELLIJ_CLASSPATH_JAR_PATTERN = Pattern . compile ( ".*classpath(\\d+)?\\.jar" ) ;
@ -84,19 +90,44 @@ final class ModifiedClassPathClassLoader extends URLClassLoader {
return super . loadClass ( name ) ;
return super . loadClass ( name ) ;
}
}
static ModifiedClassPathClassLoader get ( Class < ? > testClass ) {
static ModifiedClassPathClassLoader get ( Class < ? > testClass , Method testMethod , List < Object > arguments ) {
return cache . computeIfAbsent ( testClass , ModifiedClassPathClassLoader : : compute ) ;
Set < AnnotatedElement > candidates = new LinkedHashSet < > ( ) ;
candidates . add ( testClass ) ;
candidates . add ( testMethod ) ;
candidates . addAll ( getAnnotatedElements ( arguments . toArray ( ) ) ) ;
List < AnnotatedElement > annotatedElements = candidates . stream ( )
. filter ( ModifiedClassPathClassLoader : : hasAnnotation ) . collect ( Collectors . toList ( ) ) ;
if ( annotatedElements . isEmpty ( ) ) {
return null ;
}
return cache . computeIfAbsent ( annotatedElements , ( key ) - > compute ( testClass . getClassLoader ( ) , key ) ) ;
}
}
private static ModifiedClassPathClassLoader compute ( Class < ? > testClass ) {
private static Collection < AnnotatedElement > getAnnotatedElements ( Object [ ] array ) {
ClassLoader classLoader = testClass . getClassLoader ( ) ;
Set < AnnotatedElement > result = new LinkedHashSet < > ( ) ;
MergedAnnotations annotations = MergedAnnotations . from ( testClass ,
for ( Object item : array ) {
MergedAnnotations . SearchStrategy . TYPE_HIERARCHY ) ;
if ( item instanceof AnnotatedElement ) {
if ( annotations . isPresent ( ForkedClassPath . class ) & & ( annotations . isPresent ( ClassPathOverrides . class )
result . add ( ( AnnotatedElement ) item ) ;
| | annotations . isPresent ( ClassPathExclusions . class ) ) ) {
}
throw new IllegalStateException ( "@ForkedClassPath is redundant in combination with either "
else if ( ObjectUtils . isArray ( item ) ) {
+ "@ClassPathOverrides or @ClassPathExclusions" ) ;
result . addAll ( getAnnotatedElements ( ObjectUtils . toObjectArray ( item ) ) ) ;
}
}
}
return result ;
}
private static boolean hasAnnotation ( AnnotatedElement element ) {
MergedAnnotations annotations = MergedAnnotations . from ( element ,
MergedAnnotations . SearchStrategy . TYPE_HIERARCHY ) ;
return annotations . isPresent ( ForkedClassPath . class ) | | annotations . isPresent ( ClassPathOverrides . class )
| | annotations . isPresent ( ClassPathExclusions . class ) ;
}
private static ModifiedClassPathClassLoader compute ( ClassLoader classLoader ,
List < AnnotatedElement > annotatedClasses ) {
List < MergedAnnotations > annotations = annotatedClasses . stream ( )
. map ( ( source ) - > MergedAnnotations . from ( source , MergedAnnotations . SearchStrategy . TYPE_HIERARCHY ) )
. collect ( Collectors . toList ( ) ) ;
return new ModifiedClassPathClassLoader ( processUrls ( extractUrls ( classLoader ) , annotations ) ,
return new ModifiedClassPathClassLoader ( processUrls ( extractUrls ( classLoader ) , annotations ) ,
classLoader . getParent ( ) , classLoader ) ;
classLoader . getParent ( ) , classLoader ) ;
}
}
@ -174,9 +205,9 @@ final class ModifiedClassPathClassLoader extends URLClassLoader {
}
}
}
}
private static URL [ ] processUrls ( URL [ ] urls , MergedAnnotations annotations ) {
private static URL [ ] processUrls ( URL [ ] urls , List< MergedAnnotations> annotations ) {
ClassPathEntryFilter filter = new ClassPathEntryFilter ( annotations .get ( ClassPathExclusions . class ) );
ClassPathEntryFilter filter = new ClassPathEntryFilter ( annotations );
List < URL > additionalUrls = getAdditionalUrls ( annotations .get ( ClassPathOverrides . class ) );
List < URL > additionalUrls = getAdditionalUrls ( annotations );
List < URL > processedUrls = new ArrayList < > ( additionalUrls ) ;
List < URL > processedUrls = new ArrayList < > ( additionalUrls ) ;
for ( URL url : urls ) {
for ( URL url : urls ) {
if ( ! filter . isExcluded ( url ) ) {
if ( ! filter . isExcluded ( url ) ) {
@ -186,11 +217,15 @@ final class ModifiedClassPathClassLoader extends URLClassLoader {
return processedUrls . toArray ( new URL [ 0 ] ) ;
return processedUrls . toArray ( new URL [ 0 ] ) ;
}
}
private static List < URL > getAdditionalUrls ( MergedAnnotation < ClassPathOverrides > annotation ) {
private static List < URL > getAdditionalUrls ( List < MergedAnnotations > annotations ) {
if ( ! annotation . isPresent ( ) ) {
Set < URL > urls = new LinkedHashSet < > ( ) ;
return Collections . emptyList ( ) ;
for ( MergedAnnotations candidate : annotations ) {
MergedAnnotation < ClassPathOverrides > annotation = candidate . get ( ClassPathOverrides . class ) ;
if ( annotation . isPresent ( ) ) {
urls . addAll ( resolveCoordinates ( annotation . getStringArray ( MergedAnnotation . VALUE ) ) ) ;
}
}
}
return resolveCoordinates ( annotation . getStringArray ( MergedAnnotation . VALUE ) ) ;
return urls. stream ( ) . collect ( Collectors . toList ( ) ) ;
}
}
private static List < URL > resolveCoordinates ( String [ ] coordinates ) {
private static List < URL > resolveCoordinates ( String [ ] coordinates ) {
@ -241,9 +276,15 @@ final class ModifiedClassPathClassLoader extends URLClassLoader {
private final AntPathMatcher matcher = new AntPathMatcher ( ) ;
private final AntPathMatcher matcher = new AntPathMatcher ( ) ;
private ClassPathEntryFilter ( MergedAnnotation < ClassPathExclusions > annotation ) {
private ClassPathEntryFilter ( List < MergedAnnotations > annotations ) {
this . exclusions = annotation . getValue ( MergedAnnotation . VALUE , String [ ] . class ) . map ( Arrays : : asList )
Set < String > exclusions = new LinkedHashSet < > ( ) ;
. orElse ( Collections . emptyList ( ) ) ;
for ( MergedAnnotations candidate : annotations ) {
MergedAnnotation < ClassPathExclusions > annotation = candidate . get ( ClassPathExclusions . class ) ;
if ( annotation . isPresent ( ) ) {
exclusions . addAll ( Arrays . asList ( annotation . getStringArray ( MergedAnnotation . VALUE ) ) ) ;
}
}
this . exclusions = exclusions . stream ( ) . collect ( Collectors . toList ( ) ) ;
}
}
private boolean isExcluded ( URL url ) {
private boolean isExcluded ( URL url ) {