Java tutorial
/* * Copyright 2009-2012 Michael Tamm * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.fightinglayoutbugs; import edu.umd.cs.findbugs.*; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.config.UserPreferences; import org.apache.commons.io.FilenameUtils; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.Suite; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.newHashMap; public class FindBugsRunner extends Suite { private static class AnalyzePackageRunner extends Runner { private final List<Runner> _children; private final Description _description; private AnalyzePackageRunner(String packageName, List<Runner> children) { _children = children; _description = Description.createSuiteDescription(packageName); for (Runner child : _children) { _description.addChild(child.getDescription()); } } @Override public Description getDescription() { return _description; } @Override public void run(RunNotifier notifier) { for (Runner child : _children) { child.run(notifier); } } @Override public int testCount() { int sum = 0; for (Runner child : _children) { sum += child.testCount(); } return sum; } } private static class AnalyzeOneClassRunner extends Runner { private final Description _description; private final List<BugInstance> _bugs; private AnalyzeOneClassRunner(String className, List<BugInstance> bugs) { _description = Description.createSuiteDescription(className); _bugs = bugs; } @Override public Description getDescription() { return _description; } @Override public void run(RunNotifier notifier) { notifier.fireTestStarted(_description); try { if (!_bugs.isEmpty()) { StringBuilder sb = new StringBuilder(); int n = _bugs.size(); sb.append("Found ").append(n).append(n == 1 ? " bug:" : " bugs:"); for (BugInstance bug : _bugs) { sb.append("\n").append(bug); String sourceLocation = getSourceLocation(bug); if (sourceLocation != null) { sb.append("\n\tat ").append(sourceLocation); } } AssertionError assertionError = new AssertionError(sb.toString()); assertionError.setStackTrace(new StackTraceElement[0]); notifier.fireTestFailure(new Failure(_description, assertionError)); } } catch (Throwable e) { notifier.fireTestFailure(new Failure(_description, e)); } notifier.fireTestFinished(_description); } private String getSourceLocation(BugInstance bug) { for (BugAnnotation annotation : bug.getAnnotations()) { if (annotation instanceof SourceLineAnnotation) { SourceLineAnnotation sourceLineAnnotation = (SourceLineAnnotation) annotation; return bug.getPrimaryClass().getClassName() + "." + bug.getPrimaryMethod().getMethodName() + "(" + sourceLineAnnotation.getSourceFile() + ":" + sourceLineAnnotation.getStartLine() + ")"; } } return bug.getPrimaryClass().getClassName() + "." + bug.getPrimaryMethod().getMethodName() + "(" + bug.getPrimaryClass().getSourceFileName() + ":" + bug.getPrimaryMethod().getSourceLines().getStartLine() + ")"; } @Override public int testCount() { return 1; } } private static class Bugs { Map<String, List<BugInstance>> _bugsByClassName = newHashMap(); public void add(BugInstance bugInstance) { String className = bugInstance.getPrimaryClass().getClassName(); // If it is an inner class, add it to the bug list of the outer class ... int i = className.indexOf('$'); if (i > 0) { className = className.substring(0, i); } List<BugInstance> list = _bugsByClassName.get(className); if (list == null) { list = newArrayList(); _bugsByClassName.put(className, list); } list.add(bugInstance); } List<BugInstance> getBugs(String className) { List<BugInstance> list = _bugsByClassName.get(className); return (list == null ? Collections.<BugInstance>emptyList() : list); } } private static class BugCollector extends AbstractBugReporter { private Bugs _bugs = new Bugs(); private BugCollector() { setPriorityThreshold(Detector.NORMAL_PRIORITY); } @Override protected void doReportBug(BugInstance bugInstance) { _bugs.add(bugInstance); } @Override public void reportAnalysisError(AnalysisError error) { // noinspection ThrowableResultOfMethodCallIgnored throw new RuntimeException(error.getMessage(), error.getException()); } @Override public void reportMissingClass(String className) { } @Override public void finish() { } @Override public BugReporter getRealBugReporter() { return this; } @Override public void observeClass(ClassDescriptor classDescriptor) { } public Bugs getBugs() { return _bugs; } } private static final FileFilter ONLY_DIRS = new FileFilter() { @Override public boolean accept(File f) { return f.isDirectory() && !f.getName().startsWith("."); } }; private static final FileFilter ONLY_CLASS_FILES_NO_INNER_CLASSES = new FileFilter() { @Override public boolean accept(File f) { return f.isFile() && f.getName().endsWith(".class") && !f.getName().contains("$"); } }; private static List<Runner> getRunners(final Class<?> suiteClass) { try { List<String> classPath = getClassPath(suiteClass); File classesDir = getClassesDir(classPath); Bugs bugs = analyzeClasses(classesDir, classPath); return getRunnersForPackage("", classesDir, bugs); } catch (final Exception e) { // Initialization failed, return single runner which rethrows the caught exception ... final Description description = Description.createSuiteDescription(suiteClass.getSimpleName()); Runner runner = new Runner() { @Override public Description getDescription() { return description; } @Override public void run(RunNotifier notifier) { notifier.fireTestStarted(description); notifier.fireTestFailure(new Failure(description, e)); notifier.fireTestFinished(description); } @Override public int testCount() { return 1; } }; return Collections.singletonList(runner); } } private static List<String> getClassPath(Class<?> suiteClass) { List<String> classPath = new ArrayList<String>(); URLClassLoader classLoader = (URLClassLoader) suiteClass.getClassLoader(); do { for (URL url : classLoader.getURLs()) { final String temp = url.toString(); if (temp.startsWith("file:")) { @SuppressWarnings("deprecation") String path = URLDecoder.decode(temp.substring("file:".length())); classPath.add(path); } else { throw new RuntimeException( "Don't know how to convert class path URL '" + temp + "' into a path."); } } ClassLoader parentClassLoader = classLoader.getParent(); classLoader = (parentClassLoader instanceof URLClassLoader && parentClassLoader != classLoader ? (URLClassLoader) parentClassLoader : null); } while (classLoader != null); return classPath; } private static Bugs analyzeClasses(File classesDir, List<String> classPath) throws IOException, InterruptedException { if (!CheckBcel.check()) { throw new RuntimeException("CheckBcel.check() failed."); } FindBugs2 findBugs = new FindBugs2(); DetectorFactoryCollection detectorFactoryCollection = DetectorFactoryCollection.instance(); detectorFactoryCollection.ensureLoaded(); findBugs.setDetectorFactoryCollection(detectorFactoryCollection); BugCollector bugCollector = new BugCollector(); findBugs.setBugReporter(bugCollector); Project project = new Project(); project.addFile(classesDir.getAbsolutePath()); for (String path : classPath) { project.addAuxClasspathEntry(path); } project.setCurrentWorkingDirectory(new File("foo").getAbsoluteFile().getParentFile()); findBugs.setProject(project); findBugs.setUserPreferences(UserPreferences.createDefaultUserPreferences()); findBugs.setClassScreener(new ClassScreener()); findBugs.finishSettings(); findBugs.execute(); return bugCollector.getBugs(); } private static File getClassesDir(List<String> classPath) throws Exception { for (String path : classPath) { if (path.endsWith("/classes/") || path.endsWith("/classes")) { File classesDir = new File(path); if (!classesDir.isDirectory()) { throw new RuntimeException(path + " is not an directory."); } return classesDir; } } throw new RuntimeException("Did not find classes directory in class path: " + classPath); } private static Runner getRunnerForPackage(String packageName, File packageDir, Bugs bugs) { final List<Runner> children = getRunnersForPackage(packageName, packageDir, bugs); if (children.isEmpty()) { return null; } else if (children.size() == 1 && children.get(0) instanceof AnalyzePackageRunner) { // Compact empty package ... AnalyzePackageRunner child = (AnalyzePackageRunner) children.get(0); List<Runner> grandChildren = child._children; String childPackageName = child._description.getDisplayName(); return new AnalyzePackageRunner(childPackageName, grandChildren); } else { return new AnalyzePackageRunner(packageName, children); } } private static List<Runner> getRunnersForPackage(String packageName, File packageDir, Bugs bugs) { final List<Runner> children = new ArrayList<Runner>(); for (File subDir : packageDir.listFiles(ONLY_DIRS)) { Runner child = getRunnerForPackage( "".equals(packageName) ? subDir.getName() : packageName + "." + subDir.getName(), subDir, bugs); if (child != null) { children.add(child); } } for (File classFile : packageDir.listFiles(ONLY_CLASS_FILES_NO_INNER_CLASSES)) { String className = packageName + "." + FilenameUtils.getBaseName(classFile.getName()); Runner runner = new AnalyzeOneClassRunner(className, bugs.getBugs(className)); children.add(runner); } return children; } public FindBugsRunner(Class<?> suiteClass) throws Exception { super(suiteClass, getRunners(suiteClass)); } }