/* * <copyright> * Copyright 1997-2003 PMD for Eclipse Development team * under sponsorship of the Defense Advanced Research Projects * Agency (DARPA). * * This program is free software; you can redistribute it and/or modify * it under the terms of the Cougaar Open Source License as published by * DARPA on the Cougaar Open Source Website (www.cougaar.org). * * THE COUGAAR SOFTWARE AND ANY DERIVATIVE SUPPLIED BY LICENSOR IS * PROVIDED "AS IS" WITHOUT WARRANTIES OF ANY KIND, WHETHER EXPRESS OR * IMPLIED, INCLUDING (BUT NOT LIMITED TO) ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND WITHOUT * ANY WARRANTIES AS TO NON-INFRINGEMENT. IN NO EVENT SHALL COPYRIGHT * HOLDER BE LIABLE FOR ANY DIRECT, SPECIAL, INDIRECT OR CONSEQUENTIAL * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE OF DATA OR PROFITS, * TORTIOUS CONDUCT, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THE COUGAAR SOFTWARE. * * </copyright> */ package net.sourceforge.pmd.eclipse.runtime.cmd; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.apache.log4j.Logger; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.ResourceWorkingSetFilter; import name.herlin.command.Timer; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.PMDException; import net.sourceforge.pmd.Report.ProcessingError; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.SourceCodeProcessor; import net.sourceforge.pmd.eclipse.plugin.PMDPlugin; import net.sourceforge.pmd.eclipse.runtime.PMDRuntimeConstants; import net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties; import net.sourceforge.pmd.eclipse.runtime.properties.PropertiesException; import net.sourceforge.pmd.eclipse.util.IOUtil; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; import net.sourceforge.pmd.lang.java.JavaLanguageModule; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.util.NumericConstants; import net.sourceforge.pmd.util.StringUtil; import net.sourceforge.pmd.util.datasource.DataSource; import net.sourceforge.pmd.util.datasource.ReaderDataSource; /** * Factor some useful features for visitors * * @author Philippe Herlin * */ public class BaseVisitor { private static final Logger log = Logger.getLogger(BaseVisitor.class); private IProgressMonitor monitor; private boolean useTaskMarker = false; private Map<IFile, Set<MarkerInfo2>> accumulator; // private PMDEngine pmdEngine; private RuleSet ruleSet; private int fileCount; private long pmdDuration; private IProjectProperties projectProperties; protected RuleSet hiddenRules; private PMDConfiguration configuration; /** * The constructor is protected to avoid illegal instantiation * */ protected BaseVisitor() { super(); hiddenRules = new RuleSet(); } protected PMDConfiguration configuration() { if (configuration == null) configuration = new PMDConfiguration(); return configuration; } /** * Returns the useTaskMarker. * * @return boolean */ public boolean isUseTaskMarker() { return useTaskMarker; } /** * Sets the useTaskMarker. * * @param useTaskMarker * The useTaskMarker to set */ public void setUseTaskMarker(final boolean useTaskMarker) { this.useTaskMarker = useTaskMarker; } /** * Returns the accumulator. * * @return Map */ public Map<IFile, Set<MarkerInfo2>> getAccumulator() { return accumulator; } /** * Sets the accumulator. * * @param accumulator * The accumulator to set */ public void setAccumulator(final Map<IFile, Set<MarkerInfo2>> accumulator) { this.accumulator = accumulator; } /** * @return */ public IProgressMonitor getMonitor() { return monitor; } /** * @param monitor */ public void setMonitor(final IProgressMonitor monitor) { this.monitor = monitor; } /** * Tell whether the user has required to cancel the operation * * @return */ public boolean isCanceled() { return getMonitor() == null ? false : getMonitor().isCanceled(); } /** * Begin a subtask * * @param name * the task name */ public void subTask(final String name) { if (getMonitor() != null) { getMonitor().subTask(name); } } /** * Inform of the work progress * * @param work */ public void worked(final int work) { if (getMonitor() != null) { getMonitor().worked(work); } } // /** // * @return Returns the pmdEngine. // */ // public PMDEngine getPmdEngine() { // return pmdEngine; // } // // /** // * @param pmdEngine // * The pmdEngine to set. // */ // public void setPmdEngine(final PMDEngine pmdEngine) { // this.pmdEngine = pmdEngine; // } /** * @return Returns the ruleSet. */ public RuleSet getRuleSet() { return this.ruleSet; } /** * @param ruleSet * The ruleSet to set. */ public void setRuleSet(final RuleSet ruleSet) { ruleSet.addRuleSet(hiddenRules); this.ruleSet = ruleSet; } /** * @return the number of files that has been processed */ public int getProcessedFilesCount() { return fileCount; } /** * @return actual PMD duration */ public long getActualPmdDuration() { return pmdDuration; } /** * Set the project properties (note that visitor is expected to be called one project at a time */ public void setProjectProperties(IProjectProperties projectProperties) { this.projectProperties = projectProperties; } private boolean isIncluded(IFile file) throws PropertiesException { return projectProperties.isIncludeDerivedFiles() || !projectProperties.isIncludeDerivedFiles() && !file.isDerived(); } /** * Run PMD against a resource * * @param resource * the resource to process */ protected final void reviewResource(IResource resource) { IFile file = (IFile) resource.getAdapter(IFile.class); if (file == null || file.getFileExtension() == null) return; Reader input = null; try { boolean included = isIncluded(file); log.debug("Derived files included: " + projectProperties.isIncludeDerivedFiles()); log.debug("file " + file.getName() + " is derived: " + file.isDerived()); log.debug("file checked: " + included); prepareMarkerAccumulator(file); LanguageVersionDiscoverer languageDiscoverer = new LanguageVersionDiscoverer(); LanguageVersion languageVersion = languageDiscoverer.getDefaultLanguageVersionForFile(file.getName()); // in case it is java, select the correct java version if (languageVersion != null && languageVersion.getLanguage() == LanguageRegistry.getLanguage(JavaLanguageModule.NAME)) { languageVersion = PMDPlugin.javaVersionFor(file.getProject()); } if (languageVersion != null) { configuration().setDefaultLanguageVersion(languageVersion); } log.debug("discovered language: " + languageVersion); PMDPlugin.setJavaClassLoader(configuration(), resource.getProject()); final File sourceCodeFile = file.getRawLocation().toFile(); if (included && getRuleSet().applies(sourceCodeFile) && isFileInWorkingSet(file) && languageVersion != null) { subTask("PMD checking: " + file.getName()); Timer timer = new Timer(); RuleContext context = PMD.newRuleContext(file.getName(), sourceCodeFile); context.setLanguageVersion(languageVersion); input = new InputStreamReader(file.getContents(), file.getCharset()); // getPmdEngine().processFile(input, getRuleSet(), context); // getPmdEngine().processFile(sourceCodeFile, getRuleSet(), context); DataSource dataSource = new ReaderDataSource(input, file.getName()); RuleSetFactory ruleSetFactory = new RuleSetFactory() { @Override public synchronized RuleSets createRuleSets(String referenceString) throws RuleSetNotFoundException { return new RuleSets(getRuleSet()); } }; configuration().setThreads(0); // need to disable multi threading, as the ruleset is not recreated and shared between threads... // but as we anyway have only one file to process, it won't hurt here. PMD.processFiles(configuration(), ruleSetFactory, Arrays.asList(dataSource), context, Collections.<Renderer>emptyList()); timer.stop(); pmdDuration += timer.getDuration(); if (context.getReport().hasErrors()) { StringBuilder message = new StringBuilder("There were processing errors!\n"); Iterator<ProcessingError> errors = context.getReport().errors(); while (errors.hasNext()) { ProcessingError error = errors.next(); message.append(error.getFile()).append(": ").append(error.getMsg()).append("\n"); } PMDPlugin.getDefault().logWarn(message.toString()); throw new PMDException(message.toString()); } updateMarkers(file, context, isUseTaskMarker()); worked(1); fileCount++; } else { log.debug("The file " + file.getName() + " is not in the working set"); } } catch (CoreException e) { log.error("Core exception visiting " + file.getName(), e); // TODO: // complete message } catch (PMDException e) { log.error("PMD exception visiting " + file.getName(), e); // TODO: // complete message } catch (IOException e) { log.error("IO exception visiting " + file.getName(), e); // TODO: // complete message } catch (PropertiesException e) { log.error("Properties exception visiting " + file.getName(), e); // TODO: // complete message } catch (IllegalArgumentException e) { log.error("Illegal argument", e); } finally { IOUtil.closeQuietly(input); } } /** * Test if a file is in the PMD working set * * @param file * @return true if the file should be checked */ private boolean isFileInWorkingSet(final IFile file) throws PropertiesException { boolean fileInWorkingSet = true; IWorkingSet workingSet = projectProperties.getProjectWorkingSet(); if (workingSet != null) { ResourceWorkingSetFilter filter = new ResourceWorkingSetFilter(); filter.setWorkingSet(workingSet); fileInWorkingSet = filter.select(null, null, file); } return fileInWorkingSet; } /** * Update markers list for the specified file * * @param file * the file for which markers are to be updated * @param context * a PMD context * @param fTask * indicate if a task marker should be created * @param accumulator * a map that contains impacted file and marker informations */ private int maxAllowableViolationsFor(Rule rule) { return rule.hasDescriptor(PMDRuntimeConstants.MAX_VIOLATIONS_DESCRIPTOR) ? rule.getProperty(PMDRuntimeConstants.MAX_VIOLATIONS_DESCRIPTOR) : PMDRuntimeConstants.MAX_VIOLATIONS_DESCRIPTOR.defaultValue(); } public static String markerTypeFor(RuleViolation violation) { int priorityId = violation.getRule().getPriority().getPriority(); switch (priorityId) { case 1: return PMDRuntimeConstants.PMD_MARKER_1; case 2: return PMDRuntimeConstants.PMD_MARKER_2; case 3: return PMDRuntimeConstants.PMD_MARKER_3; case 4: return PMDRuntimeConstants.PMD_MARKER_4; case 5: return PMDRuntimeConstants.PMD_MARKER_5; default: return PMDRuntimeConstants.PMD_MARKER; } } private void prepareMarkerAccumulator(IFile file) { Map<IFile, Set<MarkerInfo2>> accumulator = getAccumulator(); if (accumulator != null) { accumulator.put(file, new HashSet<MarkerInfo2>()); } } private void updateMarkers(IFile file, RuleContext context, boolean fTask) throws CoreException, PropertiesException { Map<IFile, Set<MarkerInfo2>> accumulator = getAccumulator(); Set<MarkerInfo2> markerSet = new HashSet<MarkerInfo2>(); List<Review> reviewsList = findReviewedViolations(file); Review review = new Review(); Iterator<RuleViolation> iter = context.getReport().iterator(); // final IPreferences preferences = PMDPlugin.getDefault().loadPreferences(); // final int maxViolationsPerFilePerRule = preferences.getMaxViolationsPerFilePerRule(); Map<Rule, Integer> violationsByRule = new HashMap<Rule, Integer>(); Rule rule; while (iter.hasNext()) { RuleViolation violation = iter.next(); rule = violation.getRule(); review.ruleName = rule.getName(); review.lineNumber = violation.getBeginLine(); if (reviewsList.contains(review)) { log.debug("Ignoring violation of rule " + rule.getName() + " at line " + violation.getBeginLine() + " because of a review."); continue; } Integer count = violationsByRule.get(rule); if (count == null) { count = NumericConstants.ZERO; violationsByRule.put(rule, count); } int maxViolations = maxAllowableViolationsFor(rule); if (count.intValue() < maxViolations) { // Ryan Gustafson 02/16/2008 - Always use PMD_MARKER, as people get confused as to why PMD problems don't always show up on Problems view like they do when you do build. // markerSet.add(getMarkerInfo(violation, fTask ? PMDRuntimeConstants.PMD_TASKMARKER : PMDRuntimeConstants.PMD_MARKER)); markerSet.add( getMarkerInfo(violation, markerTypeFor(violation)) ); /* if (isDfaEnabled && violation.getRule().usesDFA()) { markerSet.add(getMarkerInfo(violation, PMDRuntimeConstants.PMD_DFA_MARKER)); } else { markerSet.add(getMarkerInfo(violation, fTask ? PMDRuntimeConstants.PMD_TASKMARKER : PMDRuntimeConstants.PMD_MARKER)); } */ violationsByRule.put(rule, Integer.valueOf(count.intValue() + 1)); log.debug("Adding a violation for rule " + rule.getName() + " at line " + violation.getBeginLine()); } else { log.debug("Ignoring violation of rule " + rule.getName() + " at line " + violation.getBeginLine() + " because maximum violations has been reached for file " + file.getName()); } } if (accumulator != null) { log.debug("Adding markerSet to accumulator for file " + file.getName()); accumulator.put(file, markerSet); } } /** * Search for reviewed violations in that file * * @param file */ private List<Review> findReviewedViolations(final IFile file) { final List<Review> reviews = new ArrayList<Review>(); BufferedReader reader = null; try { int lineNumber = 0; boolean findLine = false; boolean comment = false; final Stack<String> pendingReviews = new Stack<String>(); reader = new BufferedReader(new InputStreamReader(file.getContents())); while (reader.ready()) { String line = reader.readLine(); if (line != null) { line = line.trim(); lineNumber++; if (line.startsWith("/*")) { comment = line.indexOf("*/") == -1; } else if (comment && line.indexOf("*/") != -1) { comment = false; } else if (!comment && line.startsWith(PMDRuntimeConstants.PLUGIN_STYLE_REVIEW_COMMENT)) { final String tail = line.substring(PMDRuntimeConstants.PLUGIN_STYLE_REVIEW_COMMENT.length()); final String ruleName = tail.substring(0, tail.indexOf(':')); pendingReviews.push(ruleName); findLine = true; } else if (!comment && findLine && StringUtil.isNotEmpty(line) && !line.startsWith("//")) { findLine = false; while (!pendingReviews.empty()) { // @PMD:REVIEWED:AvoidInstantiatingObjectsInLoops: // by Herlin on 01/05/05 18:36 final Review review = new Review(); review.ruleName = pendingReviews.pop(); review.lineNumber = lineNumber; reviews.add(review); } } } } // if (log.isDebugEnabled()) { // for (int i = 0; i < reviewsList.size(); i++) { // final Review review = (Review) reviewsList.get(i); // log.debug("Review : rule " + review.ruleName + ", line " + // review.lineNumber); // } // } } catch (CoreException e) { PMDPlugin.getDefault().logError("Core Exception when searching reviewed violations", e); } catch (IOException e) { PMDPlugin.getDefault().logError("IO Exception when searching reviewed violations", e); } finally { IOUtil.closeQuietly(reader); } return reviews; } private MarkerInfo2 getMarkerInfo(RuleViolation violation, String type) throws PropertiesException { Rule rule = violation.getRule(); MarkerInfo2 info = new MarkerInfo2(type, 7); info.add(IMarker.MESSAGE, violation.getDescription()); info.add(IMarker.LINE_NUMBER, violation.getBeginLine()); info.add(PMDRuntimeConstants.KEY_MARKERATT_LINE2, violation.getEndLine()); info.add(PMDRuntimeConstants.KEY_MARKERATT_RULENAME, rule.getName()); info.add(PMDRuntimeConstants.KEY_MARKERATT_PRIORITY ,rule.getPriority().getPriority()); switch (rule.getPriority().getPriority()) { case 1: info.add(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); info.add(IMarker.SEVERITY, projectProperties.violationsAsErrors() ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING); break; case 2: if (projectProperties.violationsAsErrors()) { info.add(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); } else { info.add(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); info.add(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); } break; case 5: info.add(IMarker.SEVERITY, IMarker.SEVERITY_INFO); break; case 3: info.add(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); case 4: default: info.add(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); break; } return info; } /** * Private inner type to handle reviews */ private class Review { public String ruleName; public int lineNumber; @Override public boolean equals(final Object obj) { boolean result = false; if (obj instanceof Review) { Review reviewObj = (Review) obj; result = ruleName.equals(reviewObj.ruleName) && lineNumber == reviewObj.lineNumber; } return result; } @Override public int hashCode() { return ruleName.hashCode() + lineNumber * lineNumber; } } }