Skip to content

Commit 434f318

Browse files
committed
Makes PackageScanner include jar files
1 parent 7df5f84 commit 434f318

File tree

2 files changed

+73
-47
lines changed

2 files changed

+73
-47
lines changed

equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/PackageScanner.java

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import static nl.jqno.equalsverifier.internal.util.Rethrow.rethrow;
44

55
import java.io.File;
6+
import java.io.IOException;
7+
import java.net.URISyntaxException;
68
import java.net.URL;
79
import java.util.*;
10+
import java.util.jar.JarFile;
811
import java.util.stream.Collectors;
912
import java.util.stream.Stream;
1013

@@ -29,64 +32,79 @@ private PackageScanner() {}
2932
* @return the classes contained in the given package.
3033
*/
3134
public static List<Class<?>> getClassesIn(String packageName, PackageScanOptions options) {
32-
var result = getDirs(packageName, options)
33-
.stream()
34-
.flatMap(d -> getClassesInDir(packageName, d, options).stream())
35-
.collect(Collectors.toList());
35+
String packagePath = packageName.replace('.', '/');
36+
37+
List<Class<?>> result = getResources(packagePath)
38+
.flatMap(r -> processResource(r, packagePath, options))
39+
.map(f -> fileToClass(f, packagePath))
40+
.filter(c -> !c.isAnonymousClass())
41+
.filter(c -> !c.isLocalClass())
42+
.filter(c -> !c.getName().endsWith("Test"))
43+
.filter(
44+
c -> options.mustExtend() == null
45+
|| (options.mustExtend().isAssignableFrom(c) && !options.mustExtend().equals(c)))
46+
.collect(Collectors.toList()); // Need a mutable List for the next validations
3647

3748
Validations.validateTypesAreKnown(options.exceptClasses(), result);
3849
result.removeAll(options.exceptClasses());
3950
result.removeIf(options.exclusionPredicate());
4051
return result;
4152
}
4253

43-
private static List<File> getDirs(String packageName, PackageScanOptions options) {
54+
private static Stream<URL> getResources(String packagePath) {
4455
ClassLoader cl = Thread.currentThread().getContextClassLoader();
45-
String path = packageName.replace('.', '/');
4656
return rethrow(
47-
() -> Collections.list(cl.getResources(path)).stream().flatMap(r -> getResourcePath(r, options)).toList(),
48-
e -> "Could not scan package " + packageName);
57+
() -> Collections.list(cl.getResources(packagePath)).stream(),
58+
e -> "Could not resolve package " + packagePath + ": " + e.getMessage());
4959
}
5060

51-
private static Stream<File> getResourcePath(URL r, PackageScanOptions options) {
52-
String result = rethrow(() -> r.toURI().getPath(), e -> "Could not resolve resource path: " + e.getMessage());
53-
if (result == null) {
54-
if (options.ignoreExternalJars()) {
55-
return Stream.empty();
56-
}
57-
throw new ReflectionException("Could not resolve third-party resource " + r);
58-
}
59-
return Stream.of(new File(result));
61+
private static Stream<File> processResource(URL resource, String packagePath, PackageScanOptions options) {
62+
return rethrow(() -> switch (resource.toURI().getScheme()) {
63+
case "file" -> processDirectory(resource, options.scanRecursively());
64+
case "jar" -> options.ignoreExternalJars()
65+
? Stream.of()
66+
: walkJar(resource, packagePath, options.scanRecursively());
67+
default -> throw new ReflectionException(
68+
"Could not resolve " + resource.toURI().getScheme() + " resource " + resource);
69+
}, e -> "Could not resolve resource " + resource + ": " + e.getMessage());
70+
}
71+
72+
private static Stream<File> processDirectory(URL resource, boolean scanRecursively) throws URISyntaxException {
73+
String path = resource.toURI().getPath();
74+
return walkDirectory(new File(path), scanRecursively);
6075
}
6176

62-
private static List<Class<?>> getClassesInDir(String packageName, File dir, PackageScanOptions options) {
77+
private static Stream<File> walkDirectory(File dir, boolean scanRecursively) {
6378
if (!dir.exists()) {
64-
return Collections.emptyList();
79+
return Stream.of();
6580
}
6681
return Arrays
6782
.stream(dir.listFiles())
68-
.filter(f -> (options.scanRecursively() && f.isDirectory()) || f.getName().endsWith(".class"))
69-
.flatMap(f -> {
70-
List<Class<?>> classes;
71-
if (f.isDirectory()) {
72-
classes = getClassesInDir(packageName + "." + f.getName(), f, options);
73-
}
74-
else {
75-
classes = List.of(fileToClass(packageName, f));
76-
}
77-
return classes.stream();
78-
})
79-
.filter(c -> !c.isAnonymousClass())
80-
.filter(c -> !c.isLocalClass())
81-
.filter(c -> !c.getName().endsWith("Test"))
82-
.filter(
83-
c -> options.mustExtend() == null
84-
|| (options.mustExtend().isAssignableFrom(c) && !options.mustExtend().equals(c)))
85-
.toList();
83+
.filter(f -> (scanRecursively && f.isDirectory()) || f.getName().endsWith(".class"))
84+
.flatMap(f -> f.isDirectory() ? walkDirectory(f, scanRecursively) : Stream.of(f));
85+
}
86+
87+
private static Stream<File> walkJar(URL resource, String packagePath, boolean scanRecursively) throws IOException {
88+
String path = resource.getPath();
89+
String jar = path.substring(5, path.indexOf("!"));
90+
int packageSegments = packagePath.split("/").length;
91+
try (var file = new JarFile(jar)) {
92+
return file
93+
.stream()
94+
.map(e -> e.getName())
95+
.filter(e -> e.endsWith(".class"))
96+
.filter(e -> e.startsWith(packagePath))
97+
.filter(e -> scanRecursively || e.split("/").length == packageSegments + 1)
98+
.map(e -> new File(e))
99+
.toList()
100+
.stream();
101+
}
86102
}
87103

88-
private static Class<?> fileToClass(String packageName, File file) {
89-
String className = file.getName().substring(0, file.getName().length() - 6);
104+
private static Class<?> fileToClass(File f, String packagePath) {
105+
String className = f.getName().substring(0, f.getName().length() - 6);
106+
String fullPath = f.getParent();
107+
String packageName = fullPath.substring(fullPath.indexOf(packagePath)).replace("/", ".");
90108
return rethrow(
91109
() -> Class.forName(packageName + "." + className),
92110
e -> "Could not resolve class " + className + ", which was found in package " + packageName);

equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/PackageScannerTest.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package nl.jqno.equalsverifier.internal.reflection;
22

33
import static org.assertj.core.api.Assertions.assertThat;
4-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
54

65
import java.util.*;
76

87
import nl.jqno.equalsverifier.ScanOption;
9-
import nl.jqno.equalsverifier.internal.exceptions.ReflectionException;
108
import nl.jqno.equalsverifier.testhelpers.packages.correct.A;
119
import nl.jqno.equalsverifier.testhelpers.packages.correct.B;
1210
import nl.jqno.equalsverifier.testhelpers.packages.correct.C;
@@ -131,16 +129,26 @@ void anonymousAndLocalClassesAreSkipped() {
131129
}
132130

133131
@Test
134-
void dependencyPackage() {
135-
assertThatThrownBy(() -> PackageScanner.getClassesIn("org.junit", opts))
136-
.isInstanceOf(ReflectionException.class)
137-
.hasMessageContaining("Could not resolve third-party resource");
132+
void jarPackage() {
133+
List<Class<?>> classes = PackageScanner.getClassesIn("org.objenesis", opts);
134+
assertThat(classes)
135+
.anyMatch(c -> "org.objenesis.Objenesis".equals(c.getName()))
136+
.noneMatch(c -> "org.objenesis.instantiator.ObjectInstantiator".equals(c.getName()));
137+
}
138+
139+
@Test
140+
void jarPackageRecursive() {
141+
opts = PackageScanOptions.process(ScanOption.recursive());
142+
List<Class<?>> classes = PackageScanner.getClassesIn("org.objenesis", opts);
143+
assertThat(classes)
144+
.anyMatch(c -> "org.objenesis.Objenesis".equals(c.getName()))
145+
.anyMatch(c -> "org.objenesis.instantiator.ObjectInstantiator".equals(c.getName()));
138146
}
139147

140148
@Test
141-
void dependencyPackageWithIgnore() {
149+
void jarPackageWithIgnore() {
142150
opts = PackageScanOptions.process(ScanOption.ignoreExternalJars());
143-
List<Class<?>> classes = PackageScanner.getClassesIn("org.junit", opts);
151+
List<Class<?>> classes = PackageScanner.getClassesIn("org.objenesis", opts);
144152
assertThat(classes).isEmpty();
145153
}
146154

0 commit comments

Comments
 (0)