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.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 Report 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 writeXmlReport(report);
084
085 return report;
086
087 } finally {
088 profiler.stop();
089 Thread.currentThread().setContextClassLoader(initialClassLoader);
090 }
091 }
092
093 private RuleSets createRulesets() {
094 RuleSets rulesets = new RuleSets();
095 RuleSetFactory ruleSetFactory = new RuleSetFactory();
096
097 List<String> rulesetPaths = configuration.getRulesets();
098 LOG.info("PMD configuration: " + StringUtils.join(rulesetPaths, ", "));
099
100 for (String rulesetPath : rulesetPaths) {
101 InputStream rulesInput = openRuleset(rulesetPath);
102 rulesets.addRuleSet(ruleSetFactory.createRuleSet(rulesInput));
103 IOUtils.closeQuietly(rulesInput);
104 }
105 return rulesets;
106 }
107
108 private InputStream openRuleset(String rulesetPath) {
109 try {
110 File file = new File(rulesetPath);
111 boolean found;
112 if (file.exists()) {
113 found = true;
114 } else {
115 file = new File(project.getFileSystem().getBasedir(), rulesetPath);
116 found = file.exists();
117 }
118 if (found) {
119 return new FileInputStream(file);
120 }
121 InputStream stream = PmdExecutor.class.getResourceAsStream(rulesetPath);
122 if (stream == null) {
123 throw new SonarException("The PMD ruleset can not be found: " + rulesetPath);
124 }
125 return stream;
126
127 } catch (FileNotFoundException e) {
128 throw new SonarException("The PMD ruleset can not be found: " + rulesetPath, e);
129 }
130 }
131
132 private void writeXmlReport(Report report) throws IOException {
133 File xmlReport = configuration.getTargetXMLReport();
134 if (xmlReport != null) {
135 Renderer xmlRenderer = new XMLRenderer();
136 Writer stringwriter = new StringWriter();
137 xmlRenderer.setWriter(stringwriter);
138 xmlRenderer.start();
139 xmlRenderer.renderFileReport(report);
140 xmlRenderer.end();
141
142 LOG.info("PMD output report: " + xmlReport.getAbsolutePath());
143 FileUtils.write(xmlReport, stringwriter.toString());
144 }
145 }
146
147 static String getNormalizedJavaVersion(String javaVersion) {
148 if (StringUtils.equals("1.1", javaVersion) || StringUtils.equals("1.2", javaVersion)) {
149 javaVersion = "1.3";
150 } else if (StringUtils.equals("5", javaVersion)) {
151 javaVersion = "1.5";
152 } else if (StringUtils.equals("6", javaVersion)) {
153 javaVersion = "1.6";
154 }
155 return javaVersion;
156 }
157
158 private void setJavaVersion(PMD pmd, Project project) {
159 String javaVersion = getNormalizedJavaVersion(JavaUtils.getSourceVersion(project));
160 if (javaVersion != null) {
161 SourceType sourceType = SourceType.getSourceTypeForId("java " + javaVersion);
162 if (sourceType != null) {
163 LOG.info("Java version: " + javaVersion);
164 pmd.setJavaVersion(sourceType);
165 } else {
166 throw new SonarException("Unsupported Java version for PMD: " + javaVersion);
167 }
168 }
169 }
170 }