001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2011 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.pmd;
021
022 import net.sourceforge.pmd.*;
023 import net.sourceforge.pmd.renderers.Renderer;
024 import net.sourceforge.pmd.renderers.XMLRenderer;
025 import org.apache.commons.io.FileUtils;
026 import org.apache.commons.io.IOUtils;
027 import org.apache.commons.lang.StringUtils;
028 import org.slf4j.Logger;
029 import org.slf4j.LoggerFactory;
030 import org.sonar.api.BatchExtension;
031 import org.sonar.api.resources.Java;
032 import org.sonar.api.resources.Project;
033 import org.sonar.api.utils.SonarException;
034 import org.sonar.api.utils.TimeProfiler;
035 import org.sonar.java.api.JavaUtils;
036
037 import java.io.*;
038 import java.util.List;
039
040 public class PmdExecutor implements BatchExtension {
041
042 private static final Logger LOG = LoggerFactory.getLogger(PmdExecutor.class);
043
044 private PmdConfiguration configuration;
045 private Project project;
046
047 public PmdExecutor(Project project, PmdConfiguration configuration) {
048 this.project = project;
049 this.configuration = configuration;
050 }
051
052 public File execute() throws IOException, PMDException {
053 TimeProfiler profiler = new TimeProfiler().start("Execute PMD " + PmdVersion.getVersion());
054
055 ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
056 Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
057 try {
058 PMD pmd = new PMD();
059 setJavaVersion(pmd, project);
060 RuleContext ruleContext = new RuleContext();
061 Report report = new Report();
062 ruleContext.setReport(report);
063
064 RuleSets rulesets = createRulesets();
065
066 for (File file : project.getFileSystem().getSourceFiles(Java.INSTANCE)) {
067 ruleContext.setSourceCodeFilename(file.getAbsolutePath());
068 Reader fileReader = new InputStreamReader(new FileInputStream(file), project.getFileSystem().getSourceCharset());
069 try {
070 pmd.processFile(fileReader, rulesets, ruleContext);
071
072 } catch (PMDException e) {
073 LOG.error("Fail to execute PMD. Following file is ignored: " + file, e.getCause());
074
075 } catch (Exception e) {
076 LOG.error("Fail to execute PMD. Following file is ignored: " + file, e);
077
078 } finally {
079 IOUtils.closeQuietly(fileReader);
080 }
081 }
082
083 return writeXmlReport(project, report);
084
085 } finally {
086 profiler.stop();
087 Thread.currentThread().setContextClassLoader(initialClassLoader);
088 }
089 }
090
091 private RuleSets createRulesets() {
092 RuleSets rulesets = new RuleSets();
093 RuleSetFactory ruleSetFactory = new RuleSetFactory();
094
095 List<String> rulesetPaths = configuration.getRulesets();
096 LOG.info("PMD configuration: " + StringUtils.join(rulesetPaths, ", "));
097
098 for (String rulesetPath : rulesetPaths) {
099 InputStream rulesInput = openRuleset(rulesetPath);
100 rulesets.addRuleSet(ruleSetFactory.createRuleSet(rulesInput));
101 IOUtils.closeQuietly(rulesInput);
102 }
103 return rulesets;
104 }
105
106 private InputStream openRuleset(String rulesetPath) {
107 try {
108 File file = new File(rulesetPath);
109 boolean found;
110 if (file.exists()) {
111 found = true;
112 } else {
113 file = new File(project.getFileSystem().getBasedir(), rulesetPath);
114 found = file.exists();
115 }
116 if (found) {
117 return new FileInputStream(file);
118 }
119 InputStream stream = getClass().getResourceAsStream(rulesetPath);
120 if (stream == null) {
121 throw new RuntimeException("The PMD ruleset can not be found: " + rulesetPath);
122 }
123 return stream;
124
125 } catch (FileNotFoundException e) {
126 throw new SonarException("The PMD ruleset can not be found: " + rulesetPath, e);
127 }
128 }
129
130 private File writeXmlReport(Project project, Report report) throws IOException {
131 Renderer xmlRenderer = new XMLRenderer();
132 Writer stringwriter = new StringWriter();
133 xmlRenderer.setWriter(stringwriter);
134 xmlRenderer.start();
135 xmlRenderer.renderFileReport(report);
136 xmlRenderer.end();
137
138 File xmlReport = new File(project.getFileSystem().getSonarWorkingDirectory(), "pmd-result.xml");
139 LOG.info("PMD output report: " + xmlReport.getAbsolutePath());
140 FileUtils.write(xmlReport, stringwriter.toString());
141 return xmlReport;
142 }
143
144 static String getNormalizedJavaVersion(String javaVersion) {
145 if (StringUtils.equals("1.1", javaVersion) || StringUtils.equals("1.2", javaVersion)) {
146 javaVersion = "1.3";
147 } else if (StringUtils.equals("5", javaVersion)) {
148 javaVersion = "1.5";
149 } else if (StringUtils.equals("6", javaVersion)) {
150 javaVersion = "1.6";
151 }
152 return javaVersion;
153 }
154
155 private void setJavaVersion(PMD pmd, Project project) {
156 String javaVersion = getNormalizedJavaVersion(JavaUtils.getSourceVersion(project));
157 if (javaVersion != null) {
158 SourceType sourceType = SourceType.getSourceTypeForId("java " + javaVersion);
159 if (sourceType != null) {
160 LOG.info("Java version: " + javaVersion);
161 pmd.setJavaVersion(sourceType);
162 } else {
163 throw new SonarException("Unsupported Java version for PMD: " + javaVersion);
164 }
165 }
166 }
167 }