1919import static de .thetaphi .forbiddenapis .Checker .Option .*;
2020
2121import java .io .File ;
22+ import java .io .FileInputStream ;
2223import 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 ;
2330import java .util .Arrays ;
2431import java .util .EnumSet ;
2532import java .util .LinkedHashSet ;
2633import 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 ;
3337
3438import org .apache .commons .cli .CommandLine ;
3539import org .apache .commons .cli .DefaultParser ;
3842import org .apache .commons .cli .OptionGroup ;
3943import org .apache .commons .cli .Options ;
4044import org .codehaus .plexus .util .DirectoryScanner ;
45+ import org .codehaus .plexus .util .SelectorUtils ;
4146
4247import de .thetaphi .forbiddenapis .AsmUtils ;
4348import de .thetaphi .forbiddenapis .Checker ;
@@ -68,7 +73,7 @@ public CliMain(String... args) throws ExitException {
6873 final OptionGroup required = new OptionGroup ();
6974 required .setRequired (true );
7075 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" )
7277 .longOpt ("dir" )
7378 .hasArg ()
7479 .argName ("directory" )
@@ -215,7 +220,7 @@ private void printHelp(Options options) {
215220
216221 public void run () throws ExitException {
217222 final File classesDirectory = new File (cmd .getOptionValue (dirOpt .getLongOpt ())).getAbsoluteFile ();
218-
223+
219224 // parse classpath given as argument; add -d to classpath, too
220225 final String [] classpath = cmd .getOptionValues (classpathOpt .getLongOpt ());
221226 final URL [] urls ;
@@ -260,29 +265,6 @@ public void run() throws ExitException {
260265 checker .addSuppressAnnotation (a );
261266 }
262267
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-
286268 try {
287269 final String [] bundledSignatures = cmd .getOptionValues (bundledsignaturesOpt .getLongOpt ());
288270 if (bundledSignatures != null ) for (String bs : new LinkedHashSet <>(Arrays .asList (bundledSignatures ))) {
@@ -319,11 +301,75 @@ public void run() throws ExitException {
319301 return ;
320302 }
321303 }
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+ }
322309
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." );
327373 }
328374
329375 try {
@@ -336,6 +382,18 @@ public void run() throws ExitException {
336382 }
337383 }
338384
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+
339397 public static void main (String ... args ) {
340398 try {
341399 new CliMain (args ).run ();
0 commit comments