View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.ant.internal;
5   
6   import java.io.File;
7   import java.io.IOException;
8   import java.io.PrintWriter;
9   import java.io.StringWriter;
10  import java.util.ArrayList;
11  import java.util.LinkedList;
12  import java.util.List;
13  import java.util.concurrent.atomic.AtomicInteger;
14  import java.util.logging.Handler;
15  import java.util.logging.Level;
16  
17  import net.sourceforge.pmd.PMD;
18  import net.sourceforge.pmd.PMDConfiguration;
19  import net.sourceforge.pmd.Report;
20  import net.sourceforge.pmd.Rule;
21  import net.sourceforge.pmd.RuleContext;
22  import net.sourceforge.pmd.RulePriority;
23  import net.sourceforge.pmd.RuleSet;
24  import net.sourceforge.pmd.RuleSetFactory;
25  import net.sourceforge.pmd.RuleSetNotFoundException;
26  import net.sourceforge.pmd.RuleSets;
27  import net.sourceforge.pmd.ant.Formatter;
28  import net.sourceforge.pmd.ant.PMDTask;
29  import net.sourceforge.pmd.ant.SourceLanguage;
30  import net.sourceforge.pmd.lang.LanguageRegistry;
31  import net.sourceforge.pmd.lang.LanguageVersion;
32  import net.sourceforge.pmd.renderers.AbstractRenderer;
33  import net.sourceforge.pmd.renderers.Renderer;
34  import net.sourceforge.pmd.util.StringUtil;
35  import net.sourceforge.pmd.util.datasource.DataSource;
36  import net.sourceforge.pmd.util.datasource.FileDataSource;
37  import net.sourceforge.pmd.util.log.AntLogHandler;
38  import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
39  
40  import org.apache.commons.io.IOUtils;
41  import org.apache.tools.ant.AntClassLoader;
42  import org.apache.tools.ant.BuildException;
43  import org.apache.tools.ant.DirectoryScanner;
44  import org.apache.tools.ant.Project;
45  import org.apache.tools.ant.types.FileSet;
46  import org.apache.tools.ant.types.Path;
47  
48  public class PMDTaskImpl {
49  
50      private Path classpath;
51      private Path auxClasspath;
52      private final List<Formatter> formatters = new ArrayList<>();
53      private final List<FileSet> filesets = new ArrayList<>();
54      private final PMDConfiguration configuration = new PMDConfiguration();
55      private boolean failOnError;
56      private boolean failOnRuleViolation;
57      private int maxRuleViolations = 0;
58      private String failuresPropertyName;
59      private Project project;
60  
61      public PMDTaskImpl(PMDTask task) {
62          configuration.setReportShortNames(task.isShortFilenames());
63          configuration.setSuppressMarker(task.getSuppressMarker());
64          this.failOnError = task.isFailOnError();
65          this.failOnRuleViolation = task.isFailOnRuleViolation();
66          this.maxRuleViolations = task.getMaxRuleViolations();
67          if (this.maxRuleViolations > 0) {
68              this.failOnRuleViolation = true;
69          }
70          configuration.setRuleSets(task.getRulesetFiles());
71          if (task.getEncoding() != null) {
72              configuration.setSourceEncoding(task.getEncoding());
73          }
74          configuration.setThreads(task.getThreads());
75          this.failuresPropertyName = task.getFailuresPropertyName();
76          configuration.setMinimumPriority(RulePriority.valueOf(task.getMinimumPriority()));
77  
78          SourceLanguage version = task.getSourceLanguage();
79          if (version != null) {
80              LanguageVersion languageVersion = LanguageRegistry.findLanguageVersionByTerseName(version.getName() + " "
81                      + version.getVersion());
82              if (languageVersion == null) {
83                  throw new BuildException("The following language is not supported:" + version + ".");
84              }
85              configuration.setDefaultLanguageVersion(languageVersion);
86          }
87  
88          classpath = task.getClasspath();
89          auxClasspath = task.getAuxClasspath();
90  
91          filesets.addAll(task.getFilesets());
92          formatters.addAll(task.getFormatters());
93  
94          project = task.getProject();
95      }
96  
97      private void doTask() {
98          setupClassLoader();
99  
100         // Setup RuleSetFactory and validate RuleSets
101         RuleSetFactory ruleSetFactory = new RuleSetFactory();
102         ruleSetFactory.setClassLoader(configuration.getClassLoader());
103         try {
104             // This is just used to validate and display rules. Each thread will
105             // create its own ruleset
106             ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
107             ruleSetFactory.setWarnDeprecated(true);
108             String ruleSets = configuration.getRuleSets();
109             if (StringUtil.isNotEmpty(ruleSets)) {
110                 // Substitute env variables/properties
111                 configuration.setRuleSets(project.replaceProperties(ruleSets));
112             }
113             RuleSets rules = ruleSetFactory.createRuleSets(configuration.getRuleSets(), configuration.getPmdRuleSets());
114             ruleSetFactory.setWarnDeprecated(false);
115             logRulesUsed(rules);
116         } catch (RuleSetNotFoundException e) {
117             throw new BuildException(e.getMessage(), e);
118         }
119 
120         if (configuration.getSuppressMarker() != null) {
121             project.log("Setting suppress marker to be " + configuration.getSuppressMarker(), Project.MSG_VERBOSE);
122         }
123 
124         // Start the Formatters
125         for (Formatter formatter : formatters) {
126             project.log("Sending a report to " + formatter, Project.MSG_VERBOSE);
127             formatter.start(project.getBaseDir().toString());
128         }
129 
130         // log("Setting Language Version " + languageVersion.getShortName(),
131         // Project.MSG_VERBOSE);
132 
133         // TODO Do we really need all this in a loop over each FileSet? Seems
134         // like a lot of redundancy
135         RuleContext ctx = new RuleContext();
136         Report errorReport = new Report();
137         final AtomicInteger reportSize = new AtomicInteger();
138         final String separator = System.getProperty("file.separator");
139 
140         for (FileSet fs : filesets) {
141             List<DataSource> files = new LinkedList<>();
142             DirectoryScanner ds = fs.getDirectoryScanner(project);
143             String[] srcFiles = ds.getIncludedFiles();
144             for (String srcFile : srcFiles) {
145                 File file = new File(ds.getBasedir() + separator + srcFile);
146                 files.add(new FileDataSource(file));
147             }
148 
149             final String inputPaths = ds.getBasedir().getPath();
150             configuration.setInputPaths(inputPaths);
151 
152             Renderer logRenderer = new AbstractRenderer("log", "Logging renderer") {
153                 public void start() {
154                     // Nothing to do
155                 }
156 
157                 public void startFileAnalysis(DataSource dataSource) {
158                     project.log("Processing file " + dataSource.getNiceFileName(false, inputPaths), Project.MSG_VERBOSE);
159                 }
160 
161                 public void renderFileReport(Report r) {
162                     int size = r.size();
163                     if (size > 0) {
164                         reportSize.addAndGet(size);
165                     }
166                 }
167 
168                 public void end() {
169                     // Nothing to do
170                 }
171 
172                 public String defaultFileExtension() {
173                     return null;
174                 } // not relevant
175             };
176             List<Renderer> renderers = new LinkedList<>();
177             renderers.add(logRenderer);
178             for (Formatter formatter : formatters) {
179                 renderers.add(formatter.getRenderer());
180             }
181             try {
182                 PMD.processFiles(configuration, ruleSetFactory, files, ctx, renderers);
183             } catch (RuntimeException pmde) {
184                 handleError(ctx, errorReport, pmde);
185             }
186         }
187 
188         int problemCount = reportSize.get();
189         project.log(problemCount + " problems found", Project.MSG_VERBOSE);
190 
191         for (Formatter formatter : formatters) {
192             formatter.end(errorReport);
193         }
194 
195         if (failuresPropertyName != null && problemCount > 0) {
196             project.setProperty(failuresPropertyName, String.valueOf(problemCount));
197             project.log("Setting property " + failuresPropertyName + " to " + problemCount, Project.MSG_VERBOSE);
198         }
199 
200         if (failOnRuleViolation && problemCount > maxRuleViolations) {
201             throw new BuildException("Stopping build since PMD found " + problemCount + " rule violations in the code");
202         }
203     }
204 
205     private void handleError(RuleContext ctx, Report errorReport, RuntimeException pmde) {
206 
207         pmde.printStackTrace();
208         project.log(pmde.toString(), Project.MSG_VERBOSE);
209 
210         Throwable cause = pmde.getCause();
211 
212         if (cause != null) {
213             StringWriter strWriter = new StringWriter();
214             PrintWriter printWriter = new PrintWriter(strWriter);
215             cause.printStackTrace(printWriter);
216             project.log(strWriter.toString(), Project.MSG_VERBOSE);
217             IOUtils.closeQuietly(printWriter);
218 
219             if (StringUtil.isNotEmpty(cause.getMessage())) {
220                 project.log(cause.getMessage(), Project.MSG_VERBOSE);
221             }
222         }
223 
224         if (failOnError) {
225             throw new BuildException(pmde);
226         }
227         errorReport.addError(new Report.ProcessingError(pmde.getMessage(), ctx.getSourceCodeFilename()));
228     }
229 
230     private void setupClassLoader() {
231         if (classpath == null) {
232             classpath = new Path(project);
233         }
234         /*
235          * 'basedir' is added to the path to make sure that relative paths
236          * such as "<ruleset>resources/custom_ruleset.xml</ruleset>" still
237          * work when ant is invoked from a different directory using "-f"
238          */
239         classpath.add(new Path(null, project.getBaseDir().toString()));
240 
241         project.log("Using the AntClassLoader: " + classpath, Project.MSG_VERBOSE);
242         // must be true, otherwise you'll get ClassCastExceptions as classes
243         // are loaded twice
244         // and exist in multiple class loaders
245         boolean parentFirst = true;
246         configuration.setClassLoader(new AntClassLoader(Thread.currentThread().getContextClassLoader(), project,
247                 classpath, parentFirst));
248 
249         try {
250             if (auxClasspath != null) {
251                 project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE);
252                 configuration.prependClasspath(auxClasspath.toString());
253             }
254         } catch (IOException ioe) {
255             throw new BuildException(ioe.getMessage(), ioe);
256         }
257     }
258 
259     public void execute() throws BuildException {
260         final Handler antLogHandler = new AntLogHandler(project);
261         final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST, antLogHandler);
262         try {
263             doTask();
264         } finally {
265             logManager.close();
266         }
267     }
268 
269     private void logRulesUsed(RuleSets rules) {
270         project.log("Using these rulesets: " + configuration.getRuleSets(), Project.MSG_VERBOSE);
271 
272         RuleSet[] ruleSets = rules.getAllRuleSets();
273         for (RuleSet ruleSet : ruleSets) {
274             for (Rule rule : ruleSet.getRules()) {
275                 project.log("Using rule " + rule.getName(), Project.MSG_VERBOSE);
276             }
277         }
278     }
279 }