33import static nl .jqno .equalsverifier .internal .util .Rethrow .rethrow ;
44
55import java .io .File ;
6+ import java .io .IOException ;
7+ import java .net .URISyntaxException ;
68import java .net .URL ;
79import java .util .*;
10+ import java .util .jar .JarFile ;
811import java .util .stream .Collectors ;
912import 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 );
0 commit comments