2020 */
2121final class PackageScanner {
2222
23+ private static final String CLASS_SUFFIX = ".class" ;
24+
2325 private PackageScanner () {
2426 }
2527
28+ private static String toClassName (String resourcePath ) {
29+ return resourcePath
30+ .substring (0 , resourcePath .length () - CLASS_SUFFIX .length ())
31+ .replace ('/' , '.' )
32+ .replace ('\\' , '.' );
33+ }
34+
2635 static Set <Class <?>> scanForAnnotation (String basePackage , Class <? extends Annotation > annotation , ClassLoader loader ) {
2736 Set <String > classNames = collectClassNames (basePackage , loader );
2837 Set <Class <?>> result = new HashSet <>();
@@ -52,7 +61,9 @@ private static Set<String> collectClassNames(String basePackage, ClassLoader loa
5261 String protocol = resource .getProtocol ();
5362 if ("file" .equals (protocol )) {
5463 scanDirectory (resource , basePackage , classNames );
55- } else if ("jar" .equals (protocol ) || resource .toString ().contains ("!" )) {
64+ } else if ("jar" .equals (protocol )) {
65+ scanJar (resource , resourcePath , classNames );
66+ } else if (resource .openConnection () instanceof JarURLConnection ) {
5667 scanJar (resource , resourcePath , classNames );
5768 }
5869 }
@@ -70,13 +81,10 @@ private static void scanDirectory(URL resource, String basePackage, Set<String>
7081 }
7182 Files .walk (directory )
7283 .filter (Files ::isRegularFile )
73- .filter (path -> path .getFileName ().toString ().endsWith (".class" ))
84+ .filter (path -> path .getFileName ().toString ().endsWith (CLASS_SUFFIX ))
7485 .forEach (path -> {
7586 Path relative = directory .relativize (path );
76- String className = basePackage + '.' + relative .toString ()
77- .replace ('/' , '.' ).replace ('\\' , '.' );
78- className = className .substring (0 , className .length () - ".class" .length ());
79- classNames .add (className );
87+ classNames .add (basePackage + '.' + toClassName (relative .toString ()));
8088 });
8189 } catch (IOException | URISyntaxException e ) {
8290 throw new IllegalStateException ("Failed to scan directory for classes: " + resource , e );
@@ -86,6 +94,8 @@ private static void scanDirectory(URL resource, String basePackage, Set<String>
8694 private static void scanJar (URL resource , String resourcePath , Set <String > classNames ) {
8795 try {
8896 JarURLConnection connection = (JarURLConnection ) resource .openConnection ();
97+ // Disable caching to prevent file locking issues (especially on Windows)
98+ // and ensure proper JAR file cleanup after scanning
8999 connection .setUseCaches (false );
90100 try (JarFile jarFile = connection .getJarFile ()) {
91101 String entryPrefix = connection .getEntryName ();
@@ -102,12 +112,10 @@ private static void scanJar(URL resource, String resourcePath, Set<String> class
102112 continue ;
103113 }
104114 String name = entry .getName ();
105- if (!name .endsWith (".class" ) || !name .startsWith (entryPrefix )) {
115+ if (!name .endsWith (CLASS_SUFFIX ) || !name .startsWith (entryPrefix )) {
106116 continue ;
107117 }
108- String className = name .substring (0 , name .length () - ".class" .length ())
109- .replace ('/' , '.' );
110- classNames .add (className );
118+ classNames .add (toClassName (name ));
111119 }
112120 }
113121 } catch (IOException e ) {
0 commit comments