/*
* Created on 12 avr. 2005
*
* Copyright (c) 2005, PMD for Eclipse Development Team
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed in part by support from
* the Defense Advanced Research Project Agency (DARPA)"
* * Neither the name of "PMD for Eclipse Development Team" nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.sourceforge.pmd.eclipse.runtime.cmd;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import name.herlin.command.CommandException;
import name.herlin.command.Timer;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.eclipse.plugin.PMDPlugin;
import net.sourceforge.pmd.eclipse.runtime.PMDRuntimeConstants;
import net.sourceforge.pmd.eclipse.runtime.builder.MarkerUtil;
import net.sourceforge.pmd.eclipse.runtime.preferences.IPreferences;
import net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties;
import net.sourceforge.pmd.eclipse.runtime.properties.PropertiesException;
import net.sourceforge.pmd.eclipse.ui.actions.RuleSetUtil;
import net.sourceforge.pmd.util.StringUtil;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPerspectiveRegistry;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
/**
* This command executes the PMD engine on a specified resource
*
* @author Philippe Herlin
*
*/
public class ReviewCodeCmd extends AbstractDefaultCommand {
private final List<ISchedulingRule> resources = new ArrayList<ISchedulingRule>();
private IResourceDelta resourceDelta;
private Map<IFile, Set<MarkerInfo2>> markersByFile = new HashMap<IFile, Set<MarkerInfo2>>();
private boolean taskMarker;
private boolean openPmdPerspective;
private int ruleCount;
private int fileCount;
private long pmdDuration;
private String onErrorIssue = null;
/** Whether to run the review command, even if PMD is disabled in the project settings. */
private boolean runAlways = false;
/**
* Maximum count of changed resources, that are considered to be not a full build.
* If more than these resources are changed, PMD will only be executed, if full build option is enabled.
*/
private static final int MAXIMUM_RESOURCE_COUNT = 5;
private IProjectProperties propertyCache = null;
private static final long serialVersionUID = 1L;
private static final Logger log = Logger.getLogger(ReviewCodeCmd.class);
/**
* Default constructor
*/
public ReviewCodeCmd() {
super("ReviewCode", "Run PMD on a list of workbench resources");
setOutputProperties(true);
setReadOnly(true);
setTerminated(false);
}
public Set<IFile> markedFiles() {
return markersByFile.keySet();
}
private RuleSet currentRules() {
// FIXME
return new RuleSet();
}
private Map<Rule, String> misconfiguredRulesIn(RuleSet ruleset) {
RuleSet ruleSet = currentRules();
Map<Rule, String> faultsByRule = new HashMap<Rule, String>();
for (Rule rule : ruleSet.getRules()) {
String fault = rule.dysfunctionReason();
if (StringUtil.isNotEmpty(fault)) {
faultsByRule.put(rule, fault);
}
}
return faultsByRule;
}
private boolean checkForMisconfiguredRules() {
RuleSet ruleSet = currentRules();
if (ruleSet.getRules().isEmpty())
return true;
Map<Rule, String> faultsByRule = misconfiguredRulesIn(ruleSet);
if (faultsByRule.isEmpty())
return true;
return MessageDialog.openConfirm(Display.getDefault().getActiveShell(), "Rule configuration problem",
"Continue anyways?");
}
/**
* @see name.herlin.command.AbstractProcessableCommand#execute()
*/
@Override
public void execute() throws CommandException {
boolean doReview = checkForMisconfiguredRules();
if (!doReview)
return;
log.info("ReviewCode command starting.");
try {
fileCount = 0;
ruleCount = 0;
pmdDuration = 0;
beginTask("PMD checking...", getStepCount());
// Lancer PMD
// PMDPlugin fills resources if it's a full build and
// resourcesDelta if it is incremental or auto
if (resources.isEmpty()) {
processResourceDelta();
} else {
processResources();
}
// do we really need to do any of the rest of this if
// fileCount and ruleCount are both 0?
// skip the marking processing if the markersByFile set is empty
// (avoids grabbing the "run" lock for nothing)
if (!markersByFile.isEmpty()) {
// Appliquer les marqueurs
IWorkspaceRunnable action = new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
applyMarkers();
}
};
// clear the markers here. The call to Resource.deleteMarkers
// will
// also call the Workspace.prepareOperation so do that
// outside the larger "applyMarkers" call to avoid doubly
// holding locks
// for too long
for (IFile file : markersByFile.keySet()) {
if (isCanceled())
break;
MarkerUtil.deleteAllMarkersIn(file);
}
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
workspace.run(action, getSchedulingRule(), IWorkspace.AVOID_UPDATE, getMonitor());
}
// Switch to the PMD perspective if required
if (openPmdPerspective) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
switchToPmdPerspective();
}
});
}
} catch (CoreException e) {
throw new CommandException("Core exception when reviewing code", e);
} finally {
log.info("ReviewCode command has ended.");
setTerminated(true);
done();
// Log performance information
if (fileCount > 0 && ruleCount > 0) {
logInfo("Review code command terminated. " + ruleCount + " rules were executed against " + fileCount
+ " files. Actual PMD duration is about " + pmdDuration + "ms, that is about "
+ (float) pmdDuration / fileCount + " ms/file, " + (float) pmdDuration / ruleCount
+ " ms/rule, " + (float) pmdDuration / ((long) fileCount * (long) ruleCount) + " ms/filerule");
} else {
logInfo("Review code command terminated. " + ruleCount + " rules were executed against " + fileCount
+ " files. PMD was not executed.");
}
}
PMDPlugin.getDefault().changedFiles(markedFiles());
}
/**
* @return Returns the file markers
*/
public Map<IFile, Set<MarkerInfo2>> getMarkers() {
return markersByFile;
}
public int getFileCount() {
return fileCount;
}
/**
* @param resource
* The resource to set.
*/
public void setResources(Collection<ISchedulingRule> resources) {
resources.clear();
resources.addAll(resources);
}
/**
* Add a resource to the list of resources to be reviewed.
*
* @param resource
* a workbench resource
*/
public void addResource(IResource resource) {
if (resource == null) {
throw new IllegalArgumentException("Resource parameter can not be null");
}
resources.add(resource);
}
/**
* @param resourceDelta
* The resourceDelta to set.
*/
public void setResourceDelta(IResourceDelta resourceDelta) {
this.resourceDelta = resourceDelta;
}
/**
* @param taskMarker
* The taskMarker to set.
*/
public void setTaskMarker(boolean taskMarker) {
this.taskMarker = taskMarker;
}
public void setRunAlways(boolean runAlways) {
this.runAlways = runAlways;
}
/**
* @param openPmdPerspective
* Tell whether the PMD perspective should be opened after
* processing.
*/
public void setOpenPmdPerspective(boolean openPmdPerspective) {
this.openPmdPerspective = openPmdPerspective;
}
/**
* @see name.herlin.command.Command#reset()
*/
@Override
public void reset() {
resources.clear();
markersByFile = new HashMap<IFile, Set<MarkerInfo2>>();
setTerminated(false);
openPmdPerspective = false;
onErrorIssue = null;
runAlways = false;
}
/**
* @see name.herlin.command.Command#isReadyToExecute()
*/
@Override
public boolean isReadyToExecute() {
return resources.size() != 0 || resourceDelta != null;
}
/**
* @return the scheduling rule needed to apply markers
*/
private ISchedulingRule getSchedulingRule() {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IResourceRuleFactory ruleFactory = workspace.getRuleFactory();
ISchedulingRule rule;
if (resources.isEmpty()) {
rule = ruleFactory.markerRule(resourceDelta.getResource().getProject());
} else {
ISchedulingRule[] rules = new ISchedulingRule[resources.size()];
for (int i = 0; i < rules.length; i++) {
rules[i] = ruleFactory.markerRule((IResource) resources.get(i));
}
rule = new MultiRule(resources.toArray(rules));
}
return rule;
}
/**
* Process the list of workbench resources
*
* @throws CommandException
*/
private void processResources() throws CommandException {
final Iterator<ISchedulingRule> i = resources.iterator();
while (i.hasNext()) {
final IResource resource = (IResource) i.next();
// if resource is a project, visit only its source folders
if (resource instanceof IProject) {
processProject((IProject) resource);
} else {
processResource(resource);
}
}
}
private IProjectProperties getProjectProperties(IProject project) throws PropertiesException, CommandException {
if (propertyCache == null || !propertyCache.getProject().getName().equals(project.getName())) {
propertyCache = PMDPlugin.getDefault().loadProjectProperties(project);
}
return propertyCache;
}
private RuleSet rulesetFrom(IResource resource) throws PropertiesException, CommandException {
IProject project = resource.getProject();
IProjectProperties properties = getProjectProperties(project);
return filteredRuleSet(properties); // properties.getProjectRuleSet();
}
/**
* Review a single resource
*/
private void processResource(IResource resource) throws CommandException {
try {
final IProject project = resource.getProject();
final IProjectProperties properties = getProjectProperties(project);
if (!runAlways && !properties.isPmdEnabled()) {
return;
}
final RuleSet ruleSet = rulesetFrom(resource); // properties.getProjectRuleSet();
// final PMDEngine pmdEngine = getPmdEngineForProject(project);
int targetCount = 0;
if (resource.exists()) {
targetCount = countResourceElement(resource);
}
// Could add a property that lets us set the max number to analyze
if (properties.isFullBuildEnabled() || isUserInitiated() || targetCount <= MAXIMUM_RESOURCE_COUNT) {
setStepCount(targetCount);
log.debug("Visiting resource " + resource.getName() + " : " + getStepCount());
if (resource.exists()) {
final ResourceVisitor visitor = new ResourceVisitor();
visitor.setMonitor(getMonitor());
visitor.setRuleSet(ruleSet);
// visitor.setPmdEngine(pmdEngine);
visitor.setAccumulator(markersByFile);
visitor.setUseTaskMarker(taskMarker);
visitor.setProjectProperties(properties);
resource.accept(visitor);
ruleCount = ruleSet.getRules().size();
fileCount += visitor.getProcessedFilesCount();
pmdDuration += visitor.getActualPmdDuration();
} else {
log.debug("Skipping resource " + resource.getName() + " because it doesn't exist.");
}
} else {
String message = "Skipping resource " + resource.getName() + " because of fullBuildEnabled flag and "
+ "targetCount is " + targetCount + ". This is more than " + MAXIMUM_RESOURCE_COUNT + "."
+ " If you want to execute PMD, please check \"Full build enabled\" in the project settings";
PMDPlugin.getDefault().logInformation(message);
}
worked(1); // TODO - temp fix? BR
} catch (PropertiesException e) {
throw new CommandException(e);
} catch (CoreException e) {
throw new CommandException(e);
}
}
/**
* Review an entire project
*/
private void processProject(IProject project) throws CommandException {
try {
setStepCount(countResourceElement(project));
log.debug("Visiting project " + project.getName() + " : " + getStepCount());
if (project.hasNature(JavaCore.NATURE_ID)) {
processJavaProject(project);
} else {
processResource(project);
}
} catch (CoreException e) {
throw new CommandException(e);
}
}
private void processJavaProject(IProject project) throws CoreException, CommandException {
final IJavaProject javaProject = JavaCore.create(project);
final IClasspathEntry[] entries = javaProject.getRawClasspath();
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
for (IClasspathEntry entrie : entries) {
if (entrie.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
// phherlin note: this code is ugly but I don't how to do
// otherwise.
// The IWorkspaceRoot getContainerLocation(IPath) always
// return null.
// Catching the IllegalArgumentException on getFolder is the
// only way I found
// to know if the entry is a folder or a project !
IContainer sourceContainer = null;
try {
sourceContainer = root.getFolder(entrie.getPath());
} catch (IllegalArgumentException e) {
sourceContainer = root.getProject(entrie.getPath().toString());
}
if (sourceContainer == null) {
log.warn("Source container " + entrie.getPath() + " for project " + project.getName()
+ " is not valid");
} else {
processResource(sourceContainer);
}
}
}
}
private void taskScope(int activeRuleCount, int totalRuleCount) {
setTaskName("Checking with " + Integer.toString(activeRuleCount) + " out of "
+ Integer.toString(totalRuleCount) + " rules");
}
private RuleSet filteredRuleSet(IProjectProperties properties) throws CommandException, PropertiesException {
final RuleSet ruleSet = properties.getProjectRuleSet();
IPreferences preferences = PMDPlugin.getDefault().getPreferencesManager().loadPreferences();
Set<String> onlyActiveRuleNames = preferences.getActiveRuleNames();
RuleSet filteredRuleSet = RuleSetUtil.newCopyOf(ruleSet);
int rulesBefore = filteredRuleSet.size();
if (preferences.getGlobalRuleManagement()) {
RuleSetUtil.retainOnly(filteredRuleSet, onlyActiveRuleNames);
int rulesAfter = filteredRuleSet.size();
if (rulesAfter < rulesBefore) {
PMDPlugin.getDefault().logWarn(
"Ruleset has been filtered as Global Rule Management is active. " + rulesAfter + " of "
+ rulesBefore + " rules are active and are used. " + (rulesBefore - rulesAfter)
+ " rules will be ignored.");
}
}
filteredRuleSet.addExcludePatterns(preferences.activeExclusionPatterns());
filteredRuleSet.addIncludePatterns(preferences.activeInclusionPatterns());
filteredRuleSet.addExcludePatterns(properties.getBuildPathExcludePatterns());
filteredRuleSet.addIncludePatterns(properties.getBuildPathIncludePatterns());
taskScope(filteredRuleSet.getRules().size(), ruleSet.getRules().size());
return filteredRuleSet;
}
private RuleSet rulesetFromResourceDelta() throws PropertiesException, CommandException {
IResource resource = resourceDelta.getResource();
final IProject project = resource.getProject();
final IProjectProperties properties = getProjectProperties(project);
return filteredRuleSet(properties); // properties.getProjectRuleSet();
}
/**
* Review a resource delta
*/
private void processResourceDelta() throws CommandException {
try {
IResource resource = resourceDelta.getResource();
final IProject project = resource.getProject();
final IProjectProperties properties = getProjectProperties(project);
RuleSet ruleSet = rulesetFromResourceDelta(); // properties.getProjectRuleSet();
// PMDEngine pmdEngine = getPmdEngineForProject(project);
int targetCount = countDeltaElement(resourceDelta);
// Could add a property that lets us set the max number to analyze
if (properties.isFullBuildEnabled() || isUserInitiated() || targetCount <= MAXIMUM_RESOURCE_COUNT) {
setStepCount(targetCount);
log.debug("Visiting delta of resource " + resource.getName() + " : " + getStepCount());
DeltaVisitor visitor = new DeltaVisitor();
visitor.setMonitor(getMonitor());
visitor.setRuleSet(ruleSet);
// visitor.setPmdEngine(pmdEngine);
visitor.setAccumulator(markersByFile);
visitor.setUseTaskMarker(taskMarker);
visitor.setProjectProperties(properties);
resourceDelta.accept(visitor);
ruleCount = ruleSet.getRules().size();
fileCount += visitor.getProcessedFilesCount();
pmdDuration += visitor.getActualPmdDuration();
} else {
String message = "Skipping resourceDelta " + resource.getName() + " because of fullBuildEnabled flag and "
+ "targetCount is " + targetCount + ". This is more than " + MAXIMUM_RESOURCE_COUNT + "."
+ " If you want to execute PMD, please check \"Full build enabled\" in the project settings";
PMDPlugin.getDefault().logInformation(message);
log.debug(message);
}
} catch (PropertiesException e) {
throw new CommandException(e);
} catch (CoreException e) {
throw new CommandException(e);
}
}
/**
* Apply PMD markers after the review
*
*/
private void applyMarkers() {
log.info("Processing marker directives");
int violationCount = 0;
final Timer timer = new Timer();
String currentFile = ""; // for logging
beginTask("PMD Applying markers", markersByFile.size());
try {
for (IFile file : markersByFile.keySet()) {
if (isCanceled())
break;
currentFile = file.getName();
Set<MarkerInfo2> markerInfoSet = markersByFile.get(file);
for (MarkerInfo2 markerInfo : markerInfoSet) {
markerInfo.addAsMarkerTo(file);
violationCount++;
}
worked(1);
}
} catch (CoreException e) {
log.warn("CoreException when setting marker for file " + currentFile + " : " + e.getMessage()); // TODO:
// NLS
} finally {
timer.stop();
int count = markersByFile.size();
logInfo("" + violationCount + " markers applied on " + count + " files in " + timer.getDuration() + "ms.");
log.info("End of processing marker directives. " + violationCount + " violations for " + count + " files.");
}
}
/**
* Count the number of sub-resources of a resource
*
* @param resource
* a project
* @return the element count
*/
private int countResourceElement(IResource resource) {
if (resource instanceof IFile) {
return 1;
}
final CountVisitor visitor = new CountVisitor();
try {
resource.accept(visitor);
} catch (CoreException e) {
logError("Exception when counting elements of a project", e);
}
return visitor.count;
}
/**
* Count the number of sub-resources of a delta
*
* @param delta
* a resource delta
* @return the element count
*/
private int countDeltaElement(IResourceDelta delta) {
final CountVisitor visitor = new CountVisitor();
try {
delta.accept(visitor);
} catch (CoreException e) {
logError("Exception counting elements in a delta selection", e);
}
return visitor.count;
}
/**
* opens the PMD perspective
*
* @author SebastianRaffel ( 07.05.2005 )
*/
private static void switchToPmdPerspective() {
final IWorkbench workbench = PlatformUI.getWorkbench();
final IPerspectiveRegistry reg = workbench.getPerspectiveRegistry();
final IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
window.getActivePage().setPerspective(reg.findPerspectiveWithId(PMDRuntimeConstants.ID_PERSPECTIVE));
}
/**
* Private inner class to count the number of resources or delta elements.
* Only files are counted.
*/
private final class CountVisitor implements IResourceVisitor, IResourceDeltaVisitor {
public int count = 0;
public boolean visit(IResource resource) {
if (resource instanceof IFile) {
count++;
}
return true;
}
public boolean visit(IResourceDelta delta) {
IResource resource = delta.getResource();
return visit(resource);
}
}
}