1515import static org .junit .platform .commons .util .BlacklistedExceptions .rethrowIfBlacklisted ;
1616
1717import java .io .File ;
18- import java .io .IOException ;
1918import java .net .URL ;
2019import java .util .ArrayList ;
2120import java .util .Collections ;
2524import java .util .function .BiFunction ;
2625import java .util .function .Predicate ;
2726import java .util .function .Supplier ;
27+ import java .util .logging .Level ;
2828import java .util .logging .Logger ;
2929
3030import org .junit .platform .commons .meta .API ;
@@ -45,167 +45,182 @@ class ClasspathScanner {
4545
4646 private static final String CLASS_FILE_SUFFIX = ".class" ;
4747
48+ private static final String ROOT_PACKAGE_NAME = "" ;
49+
50+ /** Malformed class name InternalError like reported in #401. */
51+ private static final String MALFORMED_CLASS_NAME_ERROR_MESSAGE = "Malformed class name" ;
52+
4853 private final Supplier <ClassLoader > classLoaderSupplier ;
4954
5055 private final BiFunction <String , ClassLoader , Optional <Class <?>>> loadClass ;
5156
5257 ClasspathScanner (Supplier <ClassLoader > classLoaderSupplier ,
5358 BiFunction <String , ClassLoader , Optional <Class <?>>> loadClass ) {
59+
5460 this .classLoaderSupplier = classLoaderSupplier ;
5561 this .loadClass = loadClass ;
5662 }
5763
5864 boolean isPackage (String packageName ) {
5965 Preconditions .notBlank (packageName , "package name must not be null or blank" );
6066
61- String path = packagePath (packageName );
6267 try {
63- Enumeration <URL > resource = classLoaderSupplier .get ().getResources (path );
64- return resource .hasMoreElements ();
68+ return getClassLoader ().getResources (packagePath (packageName )).hasMoreElements ();
6569 }
66- catch (IOException e ) {
70+ catch (Exception ex ) {
6771 return false ;
6872 }
6973 }
7074
7175 List <Class <?>> scanForClassesInPackage (String basePackageName , Predicate <Class <?>> classFilter ) {
72- Preconditions .notBlank (basePackageName , "basePackageName must not be blank" );
76+ Preconditions .notBlank (basePackageName , "basePackageName must not be null or blank" );
7377
7478 List <File > dirs = allSourceDirsForPackage (basePackageName );
7579 return allClassesInSourceDirs (dirs , basePackageName , classFilter );
7680 }
7781
78- private List <Class <?>> allClassesInSourceDirs (List <File > sourceDirs , String basePackageName ,
79- Predicate <Class <?>> classFilter ) {
80- List <Class <?>> classes = new ArrayList <>();
81- for (File aSourceDir : sourceDirs ) {
82- classes .addAll (findClassesInSourceDirRecursively (aSourceDir , basePackageName , classFilter ));
83- }
84- return classes ;
85- }
86-
8782 List <Class <?>> scanForClassesInClasspathRoot (File root , Predicate <Class <?>> classFilter ) {
8883 Preconditions .notNull (root , "root must not be null" );
8984 Preconditions .condition (root .isDirectory (),
9085 () -> "root must be an existing directory: " + root .getAbsolutePath ());
9186
92- return findClassesInSourceDirRecursively (root , "" , classFilter );
87+ return findClassesInSourceDirRecursively (ROOT_PACKAGE_NAME , root , classFilter );
88+ }
89+
90+ private List <Class <?>> allClassesInSourceDirs (List <File > sourceDirs , String basePackageName ,
91+ Predicate <Class <?>> classFilter ) {
92+
93+ List <Class <?>> classes = new ArrayList <>();
94+ for (File sourceDir : sourceDirs ) {
95+ classes .addAll (findClassesInSourceDirRecursively (basePackageName , sourceDir , classFilter ));
96+ }
97+ return classes ;
9398 }
9499
95100 private List <File > allSourceDirsForPackage (String basePackageName ) {
96101 try {
97- ClassLoader classLoader = classLoaderSupplier .get ();
98- String path = packagePath (basePackageName );
99- Enumeration <URL > resources = classLoader .getResources (path );
102+ Enumeration <URL > resources = getClassLoader ().getResources (packagePath (basePackageName ));
100103 List <File > dirs = new ArrayList <>();
101104 while (resources .hasMoreElements ()) {
102105 URL resource = resources .nextElement ();
103106 dirs .add (new File (resource .getFile ()));
104107 }
105108 return dirs ;
106109 }
107- catch (IOException e ) {
110+ catch (Exception ex ) {
108111 return Collections .emptyList ();
109112 }
110113 }
111114
112- private String packagePath (String basePackageName ) {
113- return basePackageName .replace ('.' , '/' );
114- }
115-
116- private List <Class <?>> findClassesInSourceDirRecursively (File sourceDir , String packageName ,
115+ private List <Class <?>> findClassesInSourceDirRecursively (String packageName , File sourceDir ,
117116 Predicate <Class <?>> classFilter ) {
117+
118118 Preconditions .notNull (classFilter , "classFilter must not be null" );
119119
120- List <Class <?>> classesCollector = new ArrayList <>();
121- collectClassesRecursively (sourceDir , packageName , classesCollector , classFilter );
122- return classesCollector ;
120+ List <Class <?>> classes = new ArrayList <>();
121+ collectClassesRecursively (packageName , sourceDir , classFilter , classes );
122+ return classes ;
123123 }
124124
125- private void collectClassesRecursively (File sourceDir , String packageName , List <Class <?>> classesCollector ,
126- Predicate <Class <?>> classFilter ) {
125+ private void collectClassesRecursively (String packageName , File sourceDir , Predicate <Class <?>> classFilter ,
126+ List <Class <?>> classes ) {
127+
127128 File [] files = sourceDir .listFiles ();
128129 if (files == null ) {
129130 return ;
130131 }
132+
131133 for (File file : files ) {
132134 if (isClassFile (file )) {
133- this . handleClassFileSafely (packageName , classesCollector , classFilter , file );
135+ processClassFileSafely (packageName , file , classFilter , classes );
134136 }
135137 else if (file .isDirectory ()) {
136- collectClassesRecursively (file , appendPackageName (packageName , file .getName ()), classesCollector ,
137- classFilter );
138+ collectClassesRecursively (appendSubpackageName (packageName , file .getName ()), file , classFilter ,
139+ classes );
138140 }
139141 }
140142 }
141143
142- private void handleClassFileSafely (String packageName , List <Class <?>> classesCollector ,
143- Predicate <Class <?>> classFilter , File file ) {
144- Optional <Class <?>> classForClassFile = Optional .empty ();
144+ private void processClassFileSafely (String packageName , File classFile , Predicate <Class <?>> classFilter ,
145+ List <Class <?>> classes ) {
145146
147+ Optional <Class <?>> clazz = Optional .empty ();
146148 try {
147- classForClassFile = loadClassForClassFile ( file , packageName );
148- classForClassFile .filter (classFilter ).ifPresent (classesCollector ::add );
149+ clazz = loadClassFromFile ( packageName , classFile );
150+ clazz .filter (classFilter ).ifPresent (classes ::add );
149151 }
150152 catch (InternalError internalError ) {
151- this . catchInternalError ( file , classForClassFile , internalError );
153+ handleInternalError ( classFile , clazz , internalError );
152154 }
153155 catch (Throwable throwable ) {
154- this . catchAllOtherThrowables ( file , throwable );
156+ handleThrowable ( classFile , throwable );
155157 }
156158 }
157159
158- private void catchInternalError (File file , Optional <Class <?>> classForClassFile , InternalError internalError ) {
159- // Malformed class name InternalError as reported by #401
160- if (internalError .getMessage ().equals ("Malformed class name" )) {
161- this .logMalformedClassnameInternalError (file , classForClassFile );
160+ private Optional <Class <?>> loadClassFromFile (String packageName , File classFile ) {
161+ String className = packageName + '.'
162+ + classFile .getName ().substring (0 , classFile .getName ().length () - CLASS_FILE_SUFFIX .length ());
163+ return this .loadClass .apply (className , getClassLoader ());
164+ }
165+
166+ private void handleInternalError (File classFile , Optional <Class <?>> clazz , InternalError ex ) {
167+ if (MALFORMED_CLASS_NAME_ERROR_MESSAGE .equals (ex .getMessage ())) {
168+ logMalformedClassName (classFile , clazz , ex );
162169 }
163- // other potential InternalErrors
164170 else {
165- this . logGenericFileProcessingProblem ( file );
171+ logGenericFileProcessingException ( classFile , ex );
166172 }
167173 }
168174
169- private void logMalformedClassnameInternalError (File file , Optional <Class <?>> classForClassFile ) {
175+ private void handleThrowable (File classFile , Throwable throwable ) {
176+ rethrowIfBlacklisted (throwable );
177+ logGenericFileProcessingException (classFile , throwable );
178+ }
179+
180+ private void logMalformedClassName (File classFile , Optional <Class <?>> clazz , InternalError ex ) {
170181 try {
171- LOG .warning (() -> format ("The class in the current file has a malformed class name. Offending file: '%s'" ,
172- file .getAbsolutePath ()));
173182
174- classForClassFile .ifPresent (malformedClass ->
175- //cannot use getCanonicalName() here because its being null is related to the underlying error
176- LOG .warning (() -> format ("Malformed class name: '%s'" , malformedClass .getName ())));
183+ if (clazz .isPresent ()) {
184+ // Do not use getSimpleName() or getCanonicalName() here because they will likely
185+ // throw another exception due to the underlying error.
186+ logWarning (ex ,
187+ () -> format ("The java.lang.Class loaded from file [%s] has a malformed class name [%s]." ,
188+ classFile .getAbsolutePath (), clazz .get ().getName ()));
189+ }
190+ else {
191+ logWarning (ex , () -> format ("The java.lang.Class loaded from file [%s] has a malformed class name." ,
192+ classFile .getAbsolutePath ()));
193+ }
177194 }
178- catch (Throwable throwable ) {
179- LOG . warning (
180- "The class name of the class in the current file is so malformed that not even getName() or toString() can be called on it!" );
195+ catch (Throwable t ) {
196+ ex . addSuppressed ( t );
197+ logGenericFileProcessingException ( classFile , ex );
181198 }
182199 }
183200
184- private void catchAllOtherThrowables (File file , Throwable throwable ) {
185- rethrowIfBlacklisted (throwable );
186- this .logGenericFileProcessingProblem (file );
187- }
188-
189- private void logGenericFileProcessingProblem (File file ) {
190- LOG .warning (() -> format ("There was a problem while processing the current file. Offending file: '%s'" ,
191- file .getAbsolutePath ()));
201+ private void logGenericFileProcessingException (File classFile , Throwable throwable ) {
202+ logWarning (throwable , () -> format ("Failed to load java.lang.Class for file [%s] during classpath scanning." ,
203+ classFile .getAbsolutePath ()));
192204 }
193205
194- private String appendPackageName (String packageName , String subpackageName ) {
195- if (packageName .isEmpty ())
196- return subpackageName ;
197- else
198- return packageName + "." + subpackageName ;
206+ private String appendSubpackageName (String packageName , String subpackageName ) {
207+ return (!packageName .isEmpty () ? packageName + "." + subpackageName : subpackageName );
199208 }
200209
201- private Optional <Class <?>> loadClassForClassFile (File file , String packageName ) {
202- String className = packageName + '.'
203- + file .getName ().substring (0 , file .getName ().length () - CLASS_FILE_SUFFIX .length ());
204- return loadClass .apply (className , classLoaderSupplier .get ());
210+ private ClassLoader getClassLoader () {
211+ return this .classLoaderSupplier .get ();
205212 }
206213
207214 private static boolean isClassFile (File file ) {
208215 return file .isFile () && file .getName ().endsWith (CLASS_FILE_SUFFIX );
209216 }
210217
218+ private static String packagePath (String packageName ) {
219+ return packageName .replace ('.' , '/' );
220+ }
221+
222+ private static void logWarning (Throwable throwable , Supplier <String > msgSupplier ) {
223+ LOG .log (Level .WARNING , throwable , msgSupplier );
224+ }
225+
211226}
0 commit comments