001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.plugins.findbugs; 021 022 import com.google.common.collect.Lists; 023 import edu.umd.cs.findbugs.*; 024 import edu.umd.cs.findbugs.config.UserPreferences; 025 import org.apache.commons.io.FileUtils; 026 import org.apache.commons.io.IOUtils; 027 import org.apache.commons.io.output.NullOutputStream; 028 import org.apache.commons.lang.StringUtils; 029 import org.slf4j.Logger; 030 import org.slf4j.LoggerFactory; 031 import org.sonar.api.BatchExtension; 032 import org.sonar.api.utils.Logs; 033 import org.sonar.api.utils.SonarException; 034 import org.sonar.api.utils.TimeProfiler; 035 036 import java.io.File; 037 import java.io.IOException; 038 import java.io.OutputStream; 039 import java.io.PrintStream; 040 import java.lang.reflect.Field; 041 import java.net.URL; 042 import java.util.Enumeration; 043 import java.util.List; 044 import java.util.concurrent.Callable; 045 import java.util.concurrent.ExecutorService; 046 import java.util.concurrent.Executors; 047 import java.util.concurrent.TimeUnit; 048 049 /** 050 * @since 2.4 051 */ 052 public class FindbugsExecutor implements BatchExtension { 053 054 private static final Logger LOG = LoggerFactory.getLogger(FindbugsExecutor.class); 055 056 private FindbugsConfiguration configuration; 057 058 public FindbugsExecutor(FindbugsConfiguration configuration) { 059 this.configuration = configuration; 060 } 061 062 public File execute() { 063 TimeProfiler profiler = new TimeProfiler().start("Execute Findbugs " + FindbugsVersion.getVersion()); 064 ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader(); 065 Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader()); 066 067 OutputStream xmlOutput = null; 068 ExecutorService executorService = Executors.newSingleThreadExecutor(); 069 try { 070 DetectorFactoryCollection detectorFactory = loadFindbugsPlugins(); 071 072 final FindBugs2 engine = new FindBugs2(); 073 074 Project project = configuration.getFindbugsProject(); 075 engine.setProject(project); 076 077 XMLBugReporter xmlBugReporter = new XMLBugReporter(project); 078 xmlBugReporter.setPriorityThreshold(Detector.LOW_PRIORITY); 079 xmlBugReporter.setAddMessages(true); 080 081 File xmlReport = configuration.getTargetXMLReport(); 082 if (xmlReport != null) { 083 LOG.info("Findbugs output report: " + xmlReport.getAbsolutePath()); 084 xmlOutput = FileUtils.openOutputStream(xmlReport); 085 } else { 086 xmlOutput = new NullOutputStream(); 087 } 088 xmlBugReporter.setOutputStream(new PrintStream(xmlOutput)); 089 090 engine.setBugReporter(xmlBugReporter); 091 092 UserPreferences userPreferences = UserPreferences.createDefaultUserPreferences(); 093 userPreferences.setEffort(configuration.getEffort()); 094 engine.setUserPreferences(userPreferences); 095 096 engine.addFilter(configuration.saveIncludeConfigXml().getAbsolutePath(), true); 097 engine.addFilter(configuration.saveExcludeConfigXml().getAbsolutePath(), false); 098 099 for (File filterFile : configuration.getExcludesFilters()) { 100 if (filterFile.isFile()) { 101 engine.addFilter(filterFile.getAbsolutePath(), false); 102 } else { 103 LOG.warn("FindBugs filter-file not found: {}", filterFile); 104 } 105 } 106 107 engine.setDetectorFactoryCollection(detectorFactory); 108 engine.setAnalysisFeatureSettings(FindBugs.DEFAULT_EFFORT); 109 110 engine.finishSettings(); 111 112 executorService.submit(new FindbugsTask(engine)).get(configuration.getTimeout(), TimeUnit.MILLISECONDS); 113 114 profiler.stop(); 115 116 resetDetectorFactoryCollection(); 117 118 return xmlReport; 119 } catch (Exception e) { 120 throw new SonarException("Can not execute Findbugs", e); 121 } finally { 122 executorService.shutdown(); 123 IOUtils.closeQuietly(xmlOutput); 124 Thread.currentThread().setContextClassLoader(initialClassLoader); 125 } 126 } 127 128 private static class FindbugsTask implements Callable<Object> { 129 130 private FindBugs2 engine; 131 132 public FindbugsTask(FindBugs2 engine) { 133 this.engine = engine; 134 } 135 136 public Object call() throws Exception { 137 try { 138 engine.execute(); 139 } finally { 140 engine.dispose(); 141 } 142 return null; 143 } 144 } 145 146 private DetectorFactoryCollection loadFindbugsPlugins() { 147 List<URL> plugins = Lists.newArrayList(); 148 try { 149 Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources("findbugs.xml"); 150 while (urls.hasMoreElements()) { 151 URL url = urls.nextElement(); 152 String path = StringUtils.removeStart(StringUtils.substringBefore(url.toString(), "!"), "jar:"); 153 Logs.INFO.info("Found findbugs plugin: " + path); 154 plugins.add(new URL(path)); 155 } 156 } catch (IOException e) { 157 throw new SonarException(e); 158 } 159 160 resetDetectorFactoryCollection(); 161 DetectorFactoryCollection detectorFactory = DetectorFactoryCollection.rawInstance(); 162 detectorFactory.setPluginList(plugins.toArray(new URL[plugins.size()])); 163 for (Plugin plugin : detectorFactory.plugins()) { 164 Logs.INFO.info("Loaded plugin " + plugin.getPluginId()); 165 } 166 167 return detectorFactory; 168 } 169 170 /** 171 * Unfortunately without reflection it's impossible to reset {@link DetectorFactoryCollection}. 172 */ 173 private static void resetDetectorFactoryCollection() { 174 try { 175 Field field = DetectorFactoryCollection.class.getDeclaredField("theInstance"); 176 field.setAccessible(true); 177 field.set(null, null); 178 } catch (Exception e) { 179 throw new SonarException(e); 180 } 181 } 182 183 }