19
19
import static de .thetaphi .forbiddenapis .Checker .Option .*;
20
20
21
21
import java .io .File ;
22
+ import java .io .FileInputStream ;
22
23
import java .io .IOException ;
24
+ import java .net .JarURLConnection ;
25
+ import java .net .MalformedURLException ;
26
+ import java .net .URISyntaxException ;
27
+ import java .net .URL ;
28
+ import java .net .URLClassLoader ;
29
+ import java .net .URLConnection ;
23
30
import java .util .Arrays ;
24
31
import java .util .EnumSet ;
25
32
import java .util .LinkedHashSet ;
26
33
import java .util .Locale ;
27
- import java .net .JarURLConnection ;
28
- import java .net .URLConnection ;
29
- import java .net .URLClassLoader ;
30
- import java .net .URISyntaxException ;
31
- import java .net .URL ;
32
- import java .net .MalformedURLException ;
34
+ import java .util .regex .Pattern ;
35
+ import java .util .zip .ZipEntry ;
36
+ import java .util .zip .ZipInputStream ;
33
37
34
38
import org .apache .commons .cli .CommandLine ;
35
39
import org .apache .commons .cli .DefaultParser ;
38
42
import org .apache .commons .cli .OptionGroup ;
39
43
import org .apache .commons .cli .Options ;
40
44
import org .codehaus .plexus .util .DirectoryScanner ;
45
+ import org .codehaus .plexus .util .SelectorUtils ;
41
46
42
47
import de .thetaphi .forbiddenapis .AsmUtils ;
43
48
import de .thetaphi .forbiddenapis .Checker ;
@@ -68,7 +73,7 @@ public CliMain(String... args) throws ExitException {
68
73
final OptionGroup required = new OptionGroup ();
69
74
required .setRequired (true );
70
75
required .addOption (dirOpt = Option .builder ("d" )
71
- .desc ("directory with class files to check for forbidden api usage; this directory is also added to classpath" )
76
+ .desc ("directory (or jar file) with class files to check for forbidden api usage; this directory is also added to classpath" )
72
77
.longOpt ("dir" )
73
78
.hasArg ()
74
79
.argName ("directory" )
@@ -215,7 +220,7 @@ private void printHelp(Options options) {
215
220
216
221
public void run () throws ExitException {
217
222
final File classesDirectory = new File (cmd .getOptionValue (dirOpt .getLongOpt ())).getAbsoluteFile ();
218
-
223
+
219
224
// parse classpath given as argument; add -d to classpath, too
220
225
final String [] classpath = cmd .getOptionValues (classpathOpt .getLongOpt ());
221
226
final URL [] urls ;
@@ -260,29 +265,6 @@ public void run() throws ExitException {
260
265
checker .addSuppressAnnotation (a );
261
266
}
262
267
263
- logger .info ("Scanning for classes to check..." );
264
- if (!classesDirectory .exists ()) {
265
- throw new ExitException (EXIT_ERR_OTHER , "Directory with class files does not exist: " + classesDirectory );
266
- }
267
- String [] includes = cmd .getOptionValues (includesOpt .getLongOpt ());
268
- if (includes == null || includes .length == 0 ) {
269
- includes = new String [] { "**/*.class" };
270
- }
271
- final String [] excludes = cmd .getOptionValues (excludesOpt .getLongOpt ());
272
- final DirectoryScanner ds = new DirectoryScanner ();
273
- ds .setBasedir (classesDirectory );
274
- ds .setCaseSensitive (true );
275
- ds .setIncludes (includes );
276
- ds .setExcludes (excludes );
277
- ds .addDefaultExcludes ();
278
- ds .scan ();
279
- final String [] files = ds .getIncludedFiles ();
280
- if (files .length == 0 ) {
281
- throw new ExitException (EXIT_ERR_OTHER , String .format (Locale .ENGLISH ,
282
- "No classes found in directory %s (includes=%s, excludes=%s)." ,
283
- classesDirectory , Arrays .toString (includes ), Arrays .toString (excludes )));
284
- }
285
-
286
268
try {
287
269
final String [] bundledSignatures = cmd .getOptionValues (bundledsignaturesOpt .getLongOpt ());
288
270
if (bundledSignatures != null ) for (String bs : new LinkedHashSet <>(Arrays .asList (bundledSignatures ))) {
@@ -319,11 +301,75 @@ public void run() throws ExitException {
319
301
return ;
320
302
}
321
303
}
304
+
305
+ logger .info ("Scanning for classes to check..." );
306
+ if (!classesDirectory .exists ()) {
307
+ throw new ExitException (EXIT_ERR_OTHER , "Directory or zip/jar file with class files does not exist: " + classesDirectory );
308
+ }
322
309
323
- try {
324
- checker .addClassesToCheck (classesDirectory , files );
325
- } catch (IOException ioe ) {
326
- throw new ExitException (EXIT_ERR_OTHER , "Failed to load one of the given class files: " + ioe );
310
+ String [] includes = cmd .getOptionValues (includesOpt .getLongOpt ());
311
+ if (includes == null || includes .length == 0 ) {
312
+ includes = new String [] { "**/*.class" };
313
+ }
314
+ final String [] excludes = cmd .getOptionValues (excludesOpt .getLongOpt ());
315
+
316
+ if (classesDirectory .isDirectory ()) {
317
+ final DirectoryScanner ds = new DirectoryScanner ();
318
+ ds .setBasedir (classesDirectory );
319
+ ds .setCaseSensitive (true );
320
+ ds .setIncludes (includes );
321
+ ds .setExcludes (excludes );
322
+ ds .addDefaultExcludes ();
323
+ ds .scan ();
324
+ final String [] files = ds .getIncludedFiles ();
325
+ if (files .length == 0 ) {
326
+ throw new ExitException (EXIT_ERR_OTHER , String .format (Locale .ENGLISH ,
327
+ "No classes found in directory %s (includes=%s, excludes=%s)." ,
328
+ classesDirectory , Arrays .toString (includes ), Arrays .toString (excludes )));
329
+ }
330
+ try {
331
+ checker .addClassesToCheck (classesDirectory , files );
332
+ } catch (IOException ioe ) {
333
+ throw new ExitException (EXIT_ERR_OTHER , "Failed to load one of the given class files: " + ioe );
334
+ }
335
+ } else if (classesDirectory .getName ().matches ("(?i).*\\ .(zip|jar)" )) {
336
+ int filesFound = 0 ;
337
+ try (final ZipInputStream zipin = new ZipInputStream (new FileInputStream (classesDirectory ))) {
338
+ ZipEntry entry ;
339
+ while ((entry = zipin .getNextEntry ()) != null ) {
340
+ if (entry .isDirectory ()) continue ;
341
+ // ZIP files sometimes contain leading extra slash, remove it after normalization:
342
+ final String normalizedName = normalizePath (entry .getName ())
343
+ .replaceFirst ("^" + Pattern .quote (File .separator ), "" );
344
+ next :
345
+ for (final String ipattern : includes ) {
346
+ if (SelectorUtils .matchPath (normalizePattern (ipattern ), normalizedName )) {
347
+ if (excludes != null ) {
348
+ for (final String epattern : excludes ) {
349
+ if (SelectorUtils .matchPath (normalizePattern (epattern ), normalizedName )) {
350
+ break next ;
351
+ }
352
+ }
353
+ }
354
+ try {
355
+ checker .streamReadClassToCheck (zipin , entry .getName ());
356
+ filesFound ++;
357
+ } catch (IOException ioe ) {
358
+ throw new ExitException (EXIT_ERR_OTHER , String .format (Locale .ENGLISH ,
359
+ "Failed to load class file '%s' from jar/zip: %s" , entry .getName (), ioe ));
360
+ }
361
+ break next ;
362
+ }
363
+ }
364
+ }
365
+ }
366
+ if (filesFound == 0 ) {
367
+ throw new ExitException (EXIT_ERR_OTHER , String .format (Locale .ENGLISH ,
368
+ "No classes found in jar/zip file %s (includes=%s, excludes=%s)." ,
369
+ classesDirectory , Arrays .toString (includes ), Arrays .toString (excludes )));
370
+ }
371
+ } else {
372
+ throw new ExitException (EXIT_ERR_OTHER , "Classes directory parameter is neither a directory or a jar/zip file." );
327
373
}
328
374
329
375
try {
@@ -336,6 +382,18 @@ public void run() throws ExitException {
336
382
}
337
383
}
338
384
385
+ private static String normalizePattern (String pattern ) {
386
+ pattern = normalizePath (pattern );
387
+ if (pattern .endsWith (File .separator )) {
388
+ pattern += "**" ;
389
+ }
390
+ return pattern ;
391
+ }
392
+
393
+ private static String normalizePath (String name ) {
394
+ return name .trim ().replace ('/' , File .separatorChar ).replace ('\\' , File .separatorChar );
395
+ }
396
+
339
397
public static void main (String ... args ) {
340
398
try {
341
399
new CliMain (args ).run ();
0 commit comments