edu.umd.cs.findbugs.workflow.RejarClassesForAnalysis Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* 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.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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 org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import edu.umd.cs.findbugs.FindBugs;
import edu.umd.cs.findbugs.charsets.UTF8;
import edu.umd.cs.findbugs.charsets.UserTextFile;
import edu.umd.cs.findbugs.config.CommandLine;
import edu.umd.cs.findbugs.util.ClassName;
/**
* @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;
boolean onlyAnalyze = false;
boolean ignoreTimestamps = false;
RejarClassesForAnalysisCommandLine() {
addSwitch("-analyzeOnly", "only read the jars files and analyze them; don't produce new jar files");
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");
addSwitch("-ignoreTimestamps", "ignore timestamps on zip entries; use first version found");
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 auxiliary 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 {
if ("-analyzeOnly".equals(option)) {
onlyAnalyze = true;
} else if ("-ignoreTimestamps".equals(option)) {
ignoreTimestamps = true;
} else {
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 ("-prefix".equals(option)) {
prefix = new PrefixMatcher(argument);
} else if ("-exclude".equals(option)) {
exclude = new PrefixMatcher(argument);
} else if ("-inputFileList".equals(option)) {
inputFileList = argument;
} else if ("-auxFileList".equals(option)) {
auxFileList = argument;
} else if ("-maxClasses".equals(option)) {
maxClasses = Integer.parseInt(argument);
} else if ("-maxAge".equals(option)) {
maxAge = System.currentTimeMillis() - (24 * 60 * 60 * 1000L) * Integer.parseInt(argument);
} else if ("-outputDir".equals(option)) {
outputDir = new File(argument);
} else if ("-excludePattern".equals(option)) {
excludePatterns = new PatternMatcher(argument);
} else {
throw new IllegalArgumentException("Unknown option : " + option);
}
}
boolean skip(ZipEntry ze) {
return ze.getSize() > 1000000;
}
}
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 void readFromStandardInput(Collection result) throws IOException {
readFrom(result, UserTextFile.bufferedReader(System.in));
}
SortedMap analysisOutputFiles = new TreeMap<>();
public @Nonnull ZipOutputStream getZipOutputFile(String path) {
ZipOutputStream result = analysisOutputFiles.get(path);
if (result != null) {
return result;
}
SortedMap 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 void readFrom(Collection result, @WillClose Reader r) throws IOException {
BufferedReader in = new BufferedReader(r);
while (true) {
String s = in.readLine();
if (s == null) {
in.close();
return;
}
result.add(s);
}
}
int analysisCount = 1;
int auxiliaryCount = 1;
String getNextAuxiliaryFileOutput() {
String result;
if (auxiliaryCount == 1) {
result = "auxiliary.jar";
} else {
result = "auxiliary" + (auxiliaryCount) + ".jar";
}
auxiliaryCount++;
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;
}
/** For each file, give the latest timestamp */
Map copied = new HashMap<>();
/** While file should we copy it from */
Map copyFrom = new HashMap<>();
Set excluded = new HashSet<>();
TreeSet filesToAnalyze = new TreeSet<>();
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] [+] ");
RejarClassesForAnalysis doit = new RejarClassesForAnalysis(commandLine, argCount, args);
doit.execute();
}
int auxiliaryClassCount = 0;
ZipOutputStream auxiliaryOut;
final byte buffer[] = new byte[8192];
private boolean exclude(String dottedName) {
if (!Character.isJavaIdentifierStart(dottedName.charAt(0))) {
return true;
}
if (commandLine.excludePatterns != null && commandLine.excludePatterns.matches(dottedName)
|| commandLine.exclude.matches(dottedName)) {
excluded.add(dottedName);
return true;
}
return false;
}
boolean classFileFound;
public void execute() throws IOException {
ArrayList fileList = new ArrayList<>();
if (commandLine.inputFileList != null) {
readFrom(fileList, UTF8.fileReader(commandLine.inputFileList));
} else if (argCount == args.length) {
readFromStandardInput(fileList);
} else {
fileList.addAll(Arrays.asList(args).subList(argCount, args.length));
}
ArrayList auxFileList = new ArrayList<>();
if (commandLine.auxFileList != null) {
readFrom(auxFileList, UTF8.fileReader(commandLine.auxFileList));
auxFileList.removeAll(fileList);
}
List inputZipFiles = new ArrayList<>(fileList.size());
List auxZipFiles = new ArrayList<>(auxFileList.size());
for (String fInName : fileList) {
final 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();
classFileFound = false;
if (processZipEntries(f, new ZipElementHandler() {
boolean checked = false;
@Override
public void handle(ZipFile file, ZipEntry ze) throws IOException {
if (commandLine.skip(ze)) {
return;
}
String name = ze.getName();
String dottedName = ClassName.toDottedClassName(name);
if (exclude(dottedName)) {
return;
}
if (!checked) {
checked = true;
if (embeddedNameMismatch(file, ze)) {
System.out.println("Class name mismatch for " + name + " in " + file.getName());
throw new ClassFileNameMismatch();
}
}
if (!commandLine.prefix.matches(dottedName)) {
return;
}
classFileFound = true;
long timestamp = ze.getTime();
Long oldTimestamp = copied.get(name);
if (oldTimestamp == null) {
copied.put(name, timestamp);
copyFrom.put(name, f);
filesToAnalyze.add(name);
numFilesToAnalyze++;
} else if (!commandLine.ignoreTimestamps && oldTimestamp < timestamp) {
System.out.printf("Found later version of %s; switching from %s to %s%n", name, copyFrom.get(name), f);
copied.put(name, timestamp);
copyFrom.put(name, f);
}
}
/**
* @param dottedName
* @return
*/
}) && oldSize < copied.size()) {
inputZipFiles.add(f);
} else if (classFileFound) {
System.err.println("Skipping " + fInName + ", no new classes found");
} else {
System.err.println("Skipping " + fInName + ", no classes found");
}
}
for (String fInName : auxFileList) {
final 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();
classFileFound = false;
if (processZipEntries(f, (file, ze) -> {
if (commandLine.skip(ze)) {
return;
}
String name = ze.getName();
String dottedName = ClassName.toDottedClassName(name);
if (!exclude(dottedName)) {
classFileFound = true;
long timestamp = ze.getTime();
Long oldTimestamp = copied.get(name);
if (oldTimestamp == null) {
copied.put(name, timestamp);
copyFrom.put(name, f);
} else if (!commandLine.ignoreTimestamps && oldTimestamp < timestamp) {
copied.put(name, timestamp);
copyFrom.put(name, f);
}
}
}) && oldSize < copied.size()) {
auxZipFiles.add(f);
} else if (classFileFound) {
System.err.println("Skipping aux file " + fInName + ", no new classes found");
} else {
System.err.println("Skipping aux file" + fInName + ", no classes found");
}
}
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 (commandLine.onlyAnalyze) {
return;
}
if (numFilesToAnalyze < copied.size() || numFilesToAnalyze > commandLine.maxClasses) {
auxiliaryOut = createZipFile(getNextAuxiliaryFileOutput());
}
int count = Integer.MAX_VALUE;
String oldBaseClass = "x x";
String oldPackage = "x x";
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);
final File ff = f;
processZipEntries(f, (zipInputFile, ze) -> {
if (commandLine.skip(ze)) {
return;
}
String name = ze.getName();
String dottedName = ClassName.toDottedClassName(name);
if (exclude(dottedName)) {
return;
}
if (!ff.equals(copyFrom.get(name))) {
return;
}
if (name.contains("DefaultProblem.class")) {
System.out.printf("%40s %40s%n", name, ff);
}
boolean writeToAnalyzeOut = false;
boolean writeToAuxiliaryOut = false;
if (commandLine.prefix.matches(dottedName)) {
writeToAnalyzeOut = true;
if (numFilesToAnalyze > commandLine.maxClasses) {
writeToAuxiliaryOut = true;
}
} else {
writeToAuxiliaryOut = auxiliaryOut != null;
}
ZipOutputStream out = null;
if (writeToAnalyzeOut) {
out = getZipOutputFile(name);
out.putNextEntry(newZipEntry(ze));
}
if (writeToAuxiliaryOut) {
auxiliaryClassCount++;
if (auxiliaryClassCount > 29999) {
auxiliaryClassCount = 0;
advanceAuxiliaryOut();
}
auxiliaryOut.putNextEntry(newZipEntry(ze));
}
copyEntry(zipInputFile, ze, writeToAnalyzeOut, out, writeToAuxiliaryOut, auxiliaryOut);
});
}
for (File f : auxZipFiles) {
final File ff = f;
System.err.println("Opening aux file " + f);
processZipEntries(f, (zipInputFile, ze) -> {
if (commandLine.skip(ze)) {
return;
}
String name = ze.getName();
String dottedName = ClassName.toDottedClassName(name);
if (exclude(dottedName)) {
return;
}
if (!ff.equals(copyFrom.get(name))) {
return;
}
auxiliaryClassCount++;
if (auxiliaryClassCount > 29999) {
auxiliaryClassCount = 0;
advanceAuxiliaryOut();
}
auxiliaryOut.putNextEntry(newZipEntry(ze));
copyEntry(zipInputFile, ze, false, null, true, auxiliaryOut);
});
}
if (auxiliaryOut != null) {
auxiliaryOut.close();
}
for (ZipOutputStream out : analysisOutputFiles.values()) {
out.close();
}
System.out.println("All done");
}
private ZipOutputStream createZipFile(String fileName) throws FileNotFoundException {
File newFile = new File(commandLine.outputDir, fileName);
return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(newFile)));
}
private boolean embeddedNameMismatch(ZipFile zipInputFile, ZipEntry ze) throws IOException {
InputStream zipIn = zipInputFile.getInputStream(ze);
String name = ze.getName();
JavaClass j = new ClassParser(zipIn, name).parse();
zipIn.close();
String className = j.getClassName();
String computedFileName = ClassName.toSlashedClassName(className) + ".class";
if (name.charAt(0) == '1') {
System.out.println(name);
System.out.println(" " + className);
}
if (computedFileName.equals(name)) {
return false;
}
System.out.println("In " + name + " found " + className);
return true;
}
private void copyEntry(ZipFile zipInputFile, ZipEntry ze, boolean writeToAnalyzeOut, ZipOutputStream analyzeOut1,
boolean writeToAuxiliaryOut, ZipOutputStream auxiliaryOut1) 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 (writeToAuxiliaryOut) {
auxiliaryOut1.write(buffer, 0, bytesRead);
}
}
if (writeToAnalyzeOut) {
analyzeOut1.closeEntry();
}
if (writeToAuxiliaryOut) {
auxiliaryOut1.closeEntry();
}
zipIn.close();
}
private void advanceAuxiliaryOut() throws IOException, FileNotFoundException {
auxiliaryOut.close();
auxiliaryOut = createZipFile(getNextAuxiliaryFileOutput());
}
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;
}
if (!f.canRead() || f.isDirectory()) {
System.out.println("not readable: '" + f + "'");
return false;
}
if (f.length() == 0) {
System.out.println("empty zip file: '" + f + "'");
return false;
}
try (ZipFile 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);
}
}
} catch (ClassFileNameMismatch e) {
return false;
} catch (IOException e) {
System.out.println("Error processing '" + f + "'");
return false;
}
return true;
}
public ZipEntry newZipEntry(ZipEntry ze) {
ZipEntry ze2 = new ZipEntry(ze.getName());
ze2.setComment(ze.getComment());
ze2.setTime(ze.getTime());
ze2.setExtra(ze.getExtra());
return ze2;
}
static class ClassFileNameMismatch extends IOException {
}
interface ZipElementHandler {
void handle(ZipFile file, ZipEntry ze) throws IOException;
}
}