/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2003-2007 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.workflow; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import javax.annotation.Nonnull; import javax.annotation.WillClose; import edu.umd.cs.findbugs.FindBugs; import edu.umd.cs.findbugs.config.CommandLine; /** * @author William Pugh */ public class RejarClassesForAnalysis { static class RejarClassesForAnalysisCommandLine extends CommandLine { static class PatternMatcher { final Pattern [] pattern; PatternMatcher(String arg) { String [] p = arg.split(","); this.pattern = new Pattern[p.length]; for(int i = 0; i < p.length; i++) pattern[i] = Pattern.compile(p[i]); } public boolean matches(String arg) { for(Pattern p : pattern) if (p.matcher(arg).find()) return true; return false; } } static class PrefixMatcher { final String [] prefixes; PrefixMatcher(String arg) { this.prefixes = arg.split(","); } PrefixMatcher() { this.prefixes = new String[0]; } public boolean matches(String arg) { for(String p : prefixes) if (arg.startsWith(p)) return true; return false; } public boolean matchesEverything() { for(String p : prefixes) if (p.length() == 0) return true; return false; } } PrefixMatcher prefix = new PrefixMatcher(""); PrefixMatcher exclude = new PrefixMatcher(); PatternMatcher excludePatterns = null; int maxClasses = 29999; long maxAge = Long.MIN_VALUE; public String inputFileList; public String auxFileList; RejarClassesForAnalysisCommandLine() { addOption("-maxAge", "days", "maximum age in days (ignore jar files older than this)"); addOption("-inputFileList", "filename", "text file containing names of jar files"); addOption("-auxFileList", "filename", "text file containing names of jar files for aux class path"); addOption("-maxClasses", "num", "maximum number of classes per analysis*.jar file"); addOption("-outputDir", "dir", "directory for the generated jar files"); addOption("-prefix", "class name prefix", "comma separated list of class name prefixes that should be analyzed (e.g., edu.umd.cs.)"); addOption("-exclude", "class name prefix", "comma separated list of class name prefixes that should be excluded from both analyze and auxilary jar files (e.g., java.)"); addOption("-excludePattern", "class name pattern(s)", "comma separated list of regular expressions; all classes matching them are excluded"); } File outputDir = new File("."); /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.config.CommandLine#handleOption(java.lang.String, * java.lang.String) */ @Override protected void handleOption(String option, String optionExtraPart) throws IOException { throw new IllegalArgumentException("Unknown option : " + option); } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.config.CommandLine#handleOptionWithArgument(java * .lang.String, java.lang.String) */ @Override protected void handleOptionWithArgument(String option, String argument) throws IOException { if (option.equals("-prefix")) prefix = new PrefixMatcher(argument); else if (option.equals("-exclude")) exclude = new PrefixMatcher(argument); else if (option.equals("-inputFileList")) inputFileList = argument; else if (option.equals("-auxFileList")) auxFileList = argument; else if (option.equals("-maxClasses")) maxClasses = Integer.parseInt(argument); else if (option.equals("-maxAge")) maxAge = System.currentTimeMillis() - (24 * 60 * 60 * 1000L) * Integer.parseInt(argument); else if (option.equals("-outputDir")) outputDir = new File(argument); else if (option.equals("-excludePattern")) excludePatterns = new PatternMatcher(argument); else throw new IllegalArgumentException("Unknown option : " + option); } } final RejarClassesForAnalysisCommandLine commandLine; final int argCount; final String[] args; public RejarClassesForAnalysis(RejarClassesForAnalysisCommandLine commandLine, int argCount, String[] args) { this.commandLine = commandLine; this.argCount = argCount; this.args = args; } public static List<String> readFromStandardInput() throws IOException { return readFrom(new InputStreamReader(System.in)); } SortedMap<String, ZipOutputStream> analysisOutputFiles = new TreeMap<String, ZipOutputStream>(); public @Nonnull ZipOutputStream getZipOutputFile(String path) { ZipOutputStream result = analysisOutputFiles.get(path); if (result != null) return result; SortedMap<String, ZipOutputStream> head = analysisOutputFiles.headMap(path); String matchingPath = head.lastKey(); result = analysisOutputFiles.get(matchingPath); if (result == null) throw new IllegalArgumentException("No zip output file for " + path); return result; } public static List<String> readFrom(@WillClose Reader r) throws IOException { BufferedReader in = new BufferedReader(r); List<String> lst = new LinkedList<String>(); while (true) { String s = in.readLine(); if (s == null) { in.close(); return lst; } lst.add(s); } } int analysisCount = 1; int auxilaryCount = 1; String getNextAuxilaryFileOutput() { String result; if (auxilaryCount == 1) result = "auxilary.jar"; else result = "auxilary" + (auxilaryCount) + ".jar"; auxilaryCount++; System.out.println("Starting " + result); return result; } String getNextAnalyzeFileOutput() { String result; if (analysisCount == 1) result = "analyze.jar"; else result = "analyze" + (analysisCount) + ".jar"; analysisCount++; System.out.println("Starting " + result); return result; } Set<String> copied = new HashSet<String>(); Set<String> excluded = new HashSet<String>(); TreeSet<String> filesToAnalyze = new TreeSet<String>(); int numFilesToAnalyze = 0; public static void main(String args[]) throws Exception { FindBugs.setNoAnalysis(); RejarClassesForAnalysisCommandLine commandLine = new RejarClassesForAnalysisCommandLine(); int argCount = commandLine.parse(args, 0, Integer.MAX_VALUE, "Usage: " + RejarClassesForAnalysis.class.getName() + " [options] [<jarFile>+] "); RejarClassesForAnalysis doit = new RejarClassesForAnalysis(commandLine, argCount, args); doit.execute(); } int auxilaryClassCount = 0; ZipOutputStream auxilaryOut; final byte buffer[] = new byte[8192]; private boolean exclude(String dottedName) { if (commandLine.excludePatterns != null && commandLine.excludePatterns.matches(dottedName) || commandLine.exclude.matches(dottedName)) { excluded.add(dottedName); return true; } return false; } public void execute() throws IOException { List<String> fileList; if (commandLine.inputFileList != null) fileList = readFrom(new FileReader(commandLine.inputFileList)); else if (argCount == args.length) fileList = readFromStandardInput(); else fileList = Arrays.asList(args).subList(argCount, args.length); List<String> auxFileList = Collections.emptyList(); if (commandLine.auxFileList != null) auxFileList = readFrom(new FileReader(commandLine.auxFileList)); List<File> inputZipFiles = new ArrayList<File>(fileList.size()); List<File> auxZipFiles = new ArrayList<File>(auxFileList.size()); for (String fInName : fileList) { File f = new File(fInName); if (f.lastModified() < commandLine.maxAge) { System.err.println("Skipping " + fInName + ", too old (" + new Date(f.lastModified()) + ")"); continue; } int oldSize = copied.size(); if (processZipEntries(f, new ZipElementHandler() { public void handle(ZipFile file, ZipEntry ze) { String name = ze.getName(); String dottedName = name.replace('/', '.'); if (exclude(dottedName)) return; if (copied.add(name) && commandLine.prefix.matches(dottedName) ) { filesToAnalyze.add(name); numFilesToAnalyze++; } } /** * @param dottedName * @return */ }) && oldSize < copied.size()) inputZipFiles.add(f); } for (String fInName : auxFileList) { File f = new File(fInName); if (f.lastModified() < commandLine.maxAge) { System.err.println("Skipping " + fInName + ", too old (" + new Date(f.lastModified()) + ")"); continue; } int oldSize = copied.size(); if (processZipEntries(f, new ZipElementHandler() { public void handle(ZipFile file, ZipEntry ze) { String name = ze.getName(); String dottedName = name.replace('/', '.'); if (!exclude(dottedName)) copied.add(ze.getName()); } }) && oldSize < copied.size()) auxZipFiles.add(f); } System.out.printf(" # Zip/jar files: %2d%n", inputZipFiles.size()); System.out.printf("# aux Zip/jar files: %2d%n", auxZipFiles.size()); System.out.printf("Unique class files: %6d%n", copied.size()); if (numFilesToAnalyze != copied.size()) System.out.printf(" files to analyze: %6d%n", numFilesToAnalyze); if (!excluded.isEmpty()) System.out.printf(" excluded files: %6d%n", excluded.size()); if (numFilesToAnalyze < copied.size() || numFilesToAnalyze > commandLine.maxClasses) auxilaryOut = createZipFile(getNextAuxilaryFileOutput()); copied.clear(); int count = Integer.MAX_VALUE; String oldBaseClass = "xx"; String oldPackage = "xx"; for(String path : filesToAnalyze) { int lastSlash = path.lastIndexOf('/'); String packageName = lastSlash <= 0 ? "" : path.substring(0,lastSlash-1); int firstDollar = path.indexOf('$', lastSlash); String baseClass = firstDollar < 0 ? path : path.substring(0,firstDollar-1); boolean switchOutput; if (count > commandLine.maxClasses) switchOutput = true; else if (count + 50 > commandLine.maxClasses && !baseClass.equals(oldBaseClass)) switchOutput = true; else if (count + 250 > commandLine.maxClasses && !packageName.equals(oldPackage)) switchOutput = true; else switchOutput = false; if (switchOutput) { // advance String zipFileName = getNextAnalyzeFileOutput(); analysisOutputFiles.put(path, createZipFile(zipFileName)); System.out.printf("%s%n -> %s%n", path, zipFileName); count = 0; } count++; oldPackage = packageName; oldBaseClass = baseClass; } for (File f : inputZipFiles) { System.err.println("Reading " + f); processZipEntries(f, new ZipElementHandler() { public void handle(ZipFile zipInputFile, ZipEntry ze) throws IOException { String name = ze.getName(); String dottedName = name.replace('/', '.'); if (exclude(dottedName)) return; if (!copied.add(name)) { return; } boolean writeToAnalyzeOut = false; boolean writeToAuxilaryOut = false; if (commandLine.prefix.matches(dottedName)) { writeToAnalyzeOut = true; if (numFilesToAnalyze > commandLine.maxClasses) writeToAuxilaryOut = true; } else writeToAuxilaryOut = auxilaryOut != null; ZipOutputStream out = null; if (writeToAnalyzeOut) { out = getZipOutputFile(name); out.putNextEntry(new ZipEntry(name)); } if (writeToAuxilaryOut) { auxilaryClassCount++; if (auxilaryClassCount > 29999) { auxilaryClassCount = 0; advanceAuxilaryOut(); } auxilaryOut.putNextEntry(new ZipEntry(name)); } copyEntry(zipInputFile, ze, writeToAnalyzeOut, out, writeToAuxilaryOut, auxilaryOut); } }); } for (File f : auxZipFiles) { System.err.println("Opening aux file " + f); processZipEntries(f, new ZipElementHandler() { public void handle(ZipFile zipInputFile, ZipEntry ze) throws IOException { String name = ze.getName(); String dottedName = name.replace('/', '.'); if (exclude(dottedName)) return; if (!copied.add(name)) { return; } auxilaryClassCount++; if (auxilaryClassCount > 29999) { auxilaryClassCount = 0; advanceAuxilaryOut(); } auxilaryOut.putNextEntry(new ZipEntry(name)); copyEntry(zipInputFile, ze, false, null, true, auxilaryOut); } }); } if (auxilaryOut != null) auxilaryOut.close(); for(ZipOutputStream out : analysisOutputFiles.values()) out.close(); System.out.println("All done"); } /** * @param fileName * @return * @throws FileNotFoundException */ private ZipOutputStream createZipFile(String fileName) throws FileNotFoundException { File newFile = new File(commandLine.outputDir, fileName); return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(newFile))); } private void copyEntry(ZipFile zipInputFile, ZipEntry ze, boolean writeToAnalyzeOut, ZipOutputStream analyzeOut1, boolean writeToAuxilaryOut, ZipOutputStream auxilaryOut1) throws IOException { InputStream zipIn = zipInputFile.getInputStream(ze); while (true) { int bytesRead = zipIn.read(buffer); if (bytesRead < 0) break; if (writeToAnalyzeOut) analyzeOut1.write(buffer, 0, bytesRead); if (writeToAuxilaryOut) auxilaryOut1.write(buffer, 0, bytesRead); } if (writeToAnalyzeOut) analyzeOut1.closeEntry(); if (writeToAuxilaryOut) auxilaryOut1.closeEntry(); zipIn.close(); } private void advanceAuxilaryOut() throws IOException, FileNotFoundException { auxilaryOut.close(); auxilaryOut = createZipFile(getNextAuxilaryFileOutput()); } boolean processZipEntries(File f, ZipElementHandler handler) { if (!f.exists()) { System.out.println("file not found: '"+f+"'"); return false; } if (!f.canRead() || f.isDirectory()) { System.out.println("not readable: '"+f+"'"); return false; } ZipFile zipInputFile; try { zipInputFile = new ZipFile(f); for (Enumeration<? extends ZipEntry> e = zipInputFile.entries(); e.hasMoreElements();) { ZipEntry ze = e.nextElement(); if (!ze.isDirectory() && ze.getName().endsWith(".class") && ze.getSize() != 0) handler.handle(zipInputFile, ze); } zipInputFile.close(); } catch (IOException e) { System.out.println("Error processing '"+f+"'"); e.printStackTrace(System.out); return false; } return true; } interface ZipElementHandler { void handle(ZipFile file, ZipEntry ze) throws IOException; } }