Skip to content

Commit

Permalink
Add support for JAR files as classpath roots to ClasspathScanner
Browse files Browse the repository at this point in the history
Issue: #620
  • Loading branch information
marcphilipp committed Jan 2, 2017
1 parent a9674f0 commit 0a8b45e
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 14 deletions.
Expand Up @@ -94,15 +94,13 @@ List<Class<?>> scanForClassesInPackage(String basePackageName, Predicate<Class<?
classNameFilter);
}

List<Class<?>> scanForClassesInClasspathRoot(Path root, Predicate<Class<?>> classFilter,
List<Class<?>> scanForClassesInClasspathRoot(URI root, Predicate<Class<?>> classFilter,
Predicate<String> classNameFilter) {
Preconditions.notNull(root, "root must not be null");
Preconditions.condition(Files.isDirectory(root),
() -> "root must be an existing directory: " + root.toAbsolutePath());
Preconditions.notNull(classFilter, "classFilter must not be null");
Preconditions.notNull(classNameFilter, "classNameFilter must not be null");

return findClassesForPath(root, DEFAULT_PACKAGE_NAME, classFilter, classNameFilter);
return findClassesForUri(root, DEFAULT_PACKAGE_NAME, classFilter, classNameFilter);
}

/**
Expand All @@ -126,6 +124,9 @@ private List<Class<?>> findClassesForUri(URI baseUri, String basePackageName, Pr
Path baseDir = closeablePath.getPath();
return findClassesForPath(baseDir, basePackageName, classFilter, classNameFilter);
}
catch (PreconditionViolationException ex) {
throw ex;
}
catch (Exception ex) {
logWarning(ex, () -> "Error scanning files for URI " + baseUri);
return emptyList();
Expand All @@ -134,6 +135,7 @@ private List<Class<?>> findClassesForUri(URI baseUri, String basePackageName, Pr

private List<Class<?>> findClassesForPath(Path baseDir, String basePackageName, Predicate<Class<?>> classFilter,
Predicate<String> classNameFilter) {
Preconditions.condition(Files.exists(baseDir), () -> "baseDir must exist: " + baseDir);
List<Class<?>> classes = new ArrayList<>();
try {
Files.walkFileTree(baseDir, new ClassFileVisitor(classFile -> processClassFileSafely(baseDir,
Expand Down
Expand Up @@ -15,14 +15,18 @@
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Function;

final class CloseablePath implements Closeable {

private static final String FILE_URI_SCHEME = "file";
private static final String JAR_URI_SCHEME = "jar";
private static final String JAR_FILE_EXTENSION = ".jar";
private static final String JAR_URI_SEPARATOR = "!";

private static final Closeable NULL_CLOSEABLE = () -> {
Expand All @@ -31,15 +35,27 @@ final class CloseablePath implements Closeable {
private final Path path;
private final Closeable delegate;

static CloseablePath create(URI uri) throws IOException {
static CloseablePath create(URI uri) throws IOException, URISyntaxException {
if (JAR_URI_SCHEME.equals(uri.getScheme())) {
String[] parts = uri.toString().split(JAR_URI_SEPARATOR);
FileSystem fileSystem = FileSystems.newFileSystem(URI.create(parts[0]), emptyMap());
return new CloseablePath(fileSystem.getPath(parts[1]), fileSystem);
String jarUri = parts[0];
String jarEntry = parts[1];
return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry));
}
if (uri.getScheme().equals(FILE_URI_SCHEME) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) {
return createForJarFileSystem(new URI(JAR_URI_SCHEME, uri.toString(), null),
fileSystem -> fileSystem.getRootDirectories().iterator().next());
}
return new CloseablePath(Paths.get(uri), NULL_CLOSEABLE);
}

private static CloseablePath createForJarFileSystem(URI jarUri, Function<FileSystem, Path> pathProvider)
throws IOException {
FileSystem fileSystem = FileSystems.newFileSystem(jarUri, emptyMap());
Path path = pathProvider.apply(fileSystem);
return new CloseablePath(path, fileSystem);
}

private CloseablePath(Path path, Closeable delegate) {
this.path = path;
this.delegate = delegate;
Expand Down
Expand Up @@ -23,6 +23,7 @@
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -454,8 +455,17 @@ public static Set<Path> getAllClasspathRootDirectories() {
// @formatter:on
}

/**
* @deprecated Use {@link #findAllClassesInClasspathRoot(URI, Predicate, Predicate)} instead.
*/
@Deprecated
public static List<Class<?>> findAllClassesInClasspathRoot(Path root, Predicate<Class<?>> classTester,
Predicate<String> classNameFilter) {
return findAllClassesInClasspathRoot(root.toUri(), classTester, classNameFilter);
}

public static List<Class<?>> findAllClassesInClasspathRoot(URI root, Predicate<Class<?>> classTester,
Predicate<String> classNameFilter) {
return classpathScanner.scanForClassesInClasspathRoot(root, classTester, classNameFilter);
}

Expand Down
Expand Up @@ -19,6 +19,7 @@
import static org.junit.jupiter.api.Assumptions.assumeFalse;

import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
Expand Down Expand Up @@ -123,6 +124,23 @@ void scanForClassesInClasspathRootWhenOutOfMemoryErrorOccurs() throws Exception
outOfMemoryErrorSimulationFilter, className -> true));
}

@Test
void scanForClassesInClasspathRootWithinJarFile() throws Exception {
URL jarfile = getClass().getResource("/jartest.jar");

try (URLClassLoader classLoader = new URLClassLoader(new URL[] { jarfile })) {
ClasspathScanner classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::loadClass);

List<Class<?>> classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), clazz -> true,
className -> true);
List<String> classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
assertThat(classNames).hasSize(3) //
.contains("org.junit.platform.jartest.notincluded.NotIncluded",
"org.junit.platform.jartest.included.recursive.RecursivelyIncluded",
"org.junit.platform.jartest.included.Included");
}
}

@Test
void scanForClassesInPackage() throws Exception {
List<Class<?>> classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", clazz -> true,
Expand Down Expand Up @@ -228,7 +246,7 @@ void isPackageWhenIOExceptionOccurs() {
@Test
void findAllClassesInClasspathRoot() throws Exception {
Predicate<Class<?>> thisClassOnly = clazz -> clazz == ClasspathScannerTests.class;
Path root = getTestClasspathRoot();
URI root = getTestClasspathRoot();
List<Class<?>> classes = classpathScanner.scanForClassesInClasspathRoot(root, thisClassOnly, className -> true);
assertSame(ClasspathScannerTests.class, classes.get(0));
}
Expand All @@ -255,7 +273,7 @@ void doesNotLoopInfinitelyWithCircularSymlinks(@Root Path tempDir) throws Except
Path symlink1 = Files.createSymbolicLink(tempDir.resolve("symlink1"), directory);
Files.createSymbolicLink(directory.resolve("symlink2"), symlink1);

List<Class<?>> classes = classpathScanner.scanForClassesInClasspathRoot(symlink1, clazz -> true,
List<Class<?>> classes = classpathScanner.scanForClassesInClasspathRoot(symlink1.toUri(), clazz -> true,
className -> true);

assertThat(classes).isEmpty();
Expand All @@ -269,7 +287,7 @@ private boolean inDefaultPackage(Class<?> clazz) {

@Test
void findAllClassesInClasspathRootWithFilter() throws Exception {
Path root = getTestClasspathRoot();
URI root = getTestClasspathRoot();
List<Class<?>> classes = classpathScanner.scanForClassesInClasspathRoot(root, clazz -> true, className -> true);

assertThat(classes.size()).isGreaterThanOrEqualTo(20);
Expand All @@ -285,7 +303,7 @@ void findAllClassesInClasspathRootForNullRoot() throws Exception {
@Test
void findAllClassesInClasspathRootForNonExistingRoot() throws Exception {
assertThrows(PreconditionViolationException.class,
() -> classpathScanner.scanForClassesInClasspathRoot(Paths.get("does_not_exist"), clazz -> true,
() -> classpathScanner.scanForClassesInClasspathRoot(Paths.get("does_not_exist").toUri(), clazz -> true,
className -> true));
}

Expand All @@ -299,16 +317,16 @@ void findAllClassesInClasspathRootForNullClassFilter() throws Exception {
void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws Exception {
Predicate<Class<?>> classFilter = clazz -> true;
Predicate<String> classNameFilter = name -> ClasspathScannerTests.class.getName().equals(name);
Path root = getTestClasspathRoot();
URI root = getTestClasspathRoot();

classpathScanner.scanForClassesInClasspathRoot(root, classFilter, classNameFilter);

assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class);
}

private Path getTestClasspathRoot() throws Exception {
private URI getTestClasspathRoot() throws Exception {
URL location = getClass().getProtectionDomain().getCodeSource().getLocation();
return Paths.get(location.toURI());
return location.toURI();
}

class MemberClassToBeFound {
Expand Down

0 comments on commit 0a8b45e

Please sign in to comment.