/*
* FindBugs - Find bugs in Java programs
* Copyright (C) 2003-2008, University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.plan;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.DetectorFactory;
import edu.umd.cs.findbugs.DetectorFactoryChooser;
import edu.umd.cs.findbugs.DetectorFactoryCollection;
import edu.umd.cs.findbugs.FindBugs2;
import edu.umd.cs.findbugs.Plugin;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.graph.DepthFirstSearch;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
/**
* A plan for executing Detectors on an application. Automatically assigns
* Detectors to passes and orders Detectors within each pass based on ordering
* constraints specified in the plugin descriptor(s).
*
* @author David Hovemeyer
*/
public class ExecutionPlan {
public static final boolean DEBUG = SystemProperties.getBoolean("findbugs.execplan.debug");
private List<Plugin> pluginList;
private DetectorFactoryChooser factoryChooser;
private LinkedList<AnalysisPass> passList;
private Map<String, DetectorFactory> factoryMap;
private List<DetectorOrderingConstraint> interPassConstraintList;
private List<DetectorOrderingConstraint> intraPassConstraintList;
private Set<DetectorFactory> assignedToPassSet;
/**
* Constructor. Creates an empty plan.
*/
public ExecutionPlan() {
this.pluginList = new LinkedList<Plugin>();
this.factoryChooser = new DetectorFactoryChooser() {
@Override
public boolean choose(DetectorFactory factory) {
return true;
}
@Override
public void enable(DetectorFactory factory) {
// OK...
}
};
this.passList = new LinkedList<AnalysisPass>();
this.factoryMap = new HashMap<String, DetectorFactory>();
this.interPassConstraintList = new LinkedList<DetectorOrderingConstraint>();
this.intraPassConstraintList = new LinkedList<DetectorOrderingConstraint>();
this.assignedToPassSet = new HashSet<DetectorFactory>();
}
public void dispose() {
pluginList.clear();
factoryChooser = null;
passList.clear();
factoryMap.clear();
interPassConstraintList.clear();
intraPassConstraintList.clear();
assignedToPassSet.clear();
}
/**
* Set the DetectorFactoryChooser to use to select which detectors to
* enable. This must be called before any Plugins are added to the execution
* plan.
*/
public void setDetectorFactoryChooser(DetectorFactoryChooser factoryChooser) {
this.factoryChooser = factoryChooser;
}
public boolean isActive(@DottedClassName String detectorClass) {
return factoryMap.containsKey(detectorClass);
}
public boolean isActive(Class<? extends Detector> detectorClass) {
return isActive(detectorClass.getName());
}
/**
* Add a Plugin whose Detectors should be added to the execution plan.
*/
public void addPlugin(Plugin plugin) throws OrderingConstraintException {
if (DEBUG) {
System.out.println("Adding plugin " + plugin.getPluginId() + " to execution plan");
}
pluginList.add(plugin);
// Add ordering constraints
copyTo(plugin.interPassConstraintIterator(), interPassConstraintList);
copyTo(plugin.intraPassConstraintIterator(), intraPassConstraintList);
// Add detector factories
for (DetectorFactory factory : plugin.getDetectorFactories()) {
if (DEBUG) {
System.out.println(" Detector factory " + factory.getShortName());
}
if (factoryMap.put(factory.getFullName(), factory) != null) {
throw new OrderingConstraintException("Detector " + factory.getFullName() + " is defined by more than one plugin");
}
}
}
/**
* Build the execution plan. Using the ordering constraints specified in the
* plugin descriptor(s), assigns Detectors to passes and orders the
* Detectors within those passes.
*/
public void build() throws OrderingConstraintException {
for (DetectorFactory detectorFactory : factoryMap.values()) {
detectorFactory.setEnabledButNonReporting(false);
}
ArrayList<DetectorOrderingConstraint> allConstraints = new ArrayList<DetectorOrderingConstraint>(
interPassConstraintList.size() + intraPassConstraintList.size());
allConstraints.addAll(interPassConstraintList);
allConstraints.addAll(intraPassConstraintList);
Map<String, DetectorNode> nodeMapAll = new HashMap<String, DetectorNode>();
ConstraintGraph allPassConstraintGraph = buildConstraintGraph(nodeMapAll,
new HashSet<DetectorFactory>(factoryMap.values()), allConstraints);
boolean change;
do {
change = false;
for (Iterator<DetectorNode> i = allPassConstraintGraph.vertexIterator(); i.hasNext();) {
DetectorNode end = i.next();
if (factoryChooser.choose(end.getFactory())) {
for (Iterator<ConstraintEdge> j = allPassConstraintGraph.incomingEdgeIterator(end); j.hasNext();) {
ConstraintEdge edge = j.next();
DetectorNode start = edge.getSource();
DetectorFactory startFactory = start.getFactory();
//
// Note that we only enable an otherwise-disabled
// detector
// if it was the earlier detector in a single-source
// constraint.
//
if (!factoryChooser.choose(startFactory) && edge.isSingleSource()) {
factoryChooser.enable(startFactory);
change = true;
if (DEBUG || FindBugs2.DEBUG) {
System.out.println("Dependences force enabling of " + startFactory.getFullName());
}
}
}
}
}
} while (change);
for (Iterator<Map.Entry<String, DetectorFactory>> i = factoryMap.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, DetectorFactory> e = i.next();
if (!factoryChooser.choose(e.getValue())) {
i.remove();
}
}
// Build inter-pass constraint graph
Map<String, DetectorNode> nodeMap = new HashMap<String, DetectorNode>();
ConstraintGraph interPassConstraintGraph = buildConstraintGraph(nodeMap,
new HashSet<DetectorFactory>(factoryMap.values()), interPassConstraintList);
if (DEBUG) {
System.out.println(interPassConstraintGraph.getNumVertices() + " nodes in inter-pass constraint graph");
}
// Build list of analysis passes.
// This will assign all detectors referenced in inter- or intra-pass
// ordering constraints to passes. Detectors with any ordering
// constraint will be left unassigned.
buildPassList(interPassConstraintGraph);
// Sort each pass by intra-pass ordering constraints.
// This may assign some previously unassigned detectors to passes.
for (AnalysisPass pass : passList) {
sortPass(intraPassConstraintList, factoryMap, pass);
}
// If there are any unassigned detectors remaining,
// add them to the final pass.
if (factoryMap.size() > assignedToPassSet.size()) {
AnalysisPass lastPass;
if (passList.isEmpty()) {
lastPass = new AnalysisPass();
addPass(lastPass);
} else {
lastPass = passList.getLast();
}
Set<DetectorFactory> unassignedSet = getUnassignedSet();
for (DetectorFactory factory : unassignedSet) {
assignToPass(factory, lastPass);
}
appendDetectorsToPass(unassignedSet, lastPass);
}
if (DEBUG) {
print();
}
}
/**
* Get an Iterator over the AnalysisPasses.
*/
public Iterator<AnalysisPass> passIterator() {
return passList.iterator();
}
/**
* Get the number of passes in the execution plan.
*
* @return the number of passes in the execution plan
*/
public int getNumPasses() {
return passList.size();
}
private static <T> void copyTo(Iterator<T> iter, Collection<T> dest) {
while (iter.hasNext()) {
dest.add(iter.next());
}
}
/**
* Build a constraint graph. This represents ordering constraints between
* Detectors. A topological sort of the constraint graph will yield a
* correct ordering of the detectors (which may mean either passes or an
* ordering within a single pass, depending on whether the constraints are
* inter-pass or intra-pass).
*
* @param nodeMap
* map to be populated with detector class names to constraint
* graph nodes for those detectors
* @param factorySet
* build the graph using these DetectorFactories as nodes
* @param constraintList
* List of ordering constraints
* @return the ConstraintGraph
*/
private ConstraintGraph buildConstraintGraph(Map<String, DetectorNode> nodeMap, Set<DetectorFactory> factorySet,
List<DetectorOrderingConstraint> constraintList) {
ConstraintGraph result = new ConstraintGraph();
for (DetectorOrderingConstraint constraint : constraintList) {
Set<DetectorNode> earlierSet = addOrCreateDetectorNodes(constraint.getEarlier(), nodeMap, factorySet, result);
Set<DetectorNode> laterSet = addOrCreateDetectorNodes(constraint.getLater(), nodeMap, factorySet, result);
createConstraintEdges(result, earlierSet, laterSet, constraint);
}
return result;
}
private Set<DetectorFactory> selectDetectors(DetectorFactorySelector selector, Set<DetectorFactory> candidateSet) {
Set<DetectorFactory> result = new HashSet<DetectorFactory>();
for (DetectorFactory factory : candidateSet) {
if (selector.selectFactory(factory)) {
result.add(factory);
}
}
return result;
}
private Set<DetectorNode> addOrCreateDetectorNodes(DetectorFactorySelector selector, Map<String, DetectorNode> nodeMap,
Set<DetectorFactory> factorySet, ConstraintGraph constraintGraph) {
HashSet<DetectorNode> result = new HashSet<DetectorNode>();
Set<DetectorFactory> chosenSet = selectDetectors(selector, factorySet);
for (DetectorFactory factory : chosenSet) {
DetectorNode node = addOrCreateDetectorNode(factory, nodeMap, constraintGraph);
result.add(node);
}
return result;
}
private DetectorNode addOrCreateDetectorNode(DetectorFactory factory, Map<String, DetectorNode> nodeMap,
ConstraintGraph constraintGraph) {
DetectorNode node = nodeMap.get(factory.getFullName());
if (node == null) {
node = new DetectorNode(factory);
nodeMap.put(factory.getFullName(), node);
constraintGraph.addVertex(node);
}
return node;
}
private void createConstraintEdges(ConstraintGraph result, Set<DetectorNode> earlierSet, Set<DetectorNode> laterSet,
DetectorOrderingConstraint constraint) {
// It is perfectly fine for a constraint to produce no edges
// if any detector it specifies is not enabled.
if (earlierSet.isEmpty() || laterSet.isEmpty()) {
return;
}
for (DetectorNode earlier : earlierSet) {
for (DetectorNode later : laterSet) {
ConstraintEdge edge = result.createEdge(earlier, later);
edge.setConstraint(constraint);
}
}
}
private void buildPassList(ConstraintGraph constraintGraph) throws OrderingConstraintException {
int passCount = 0;
while (constraintGraph.getNumVertices() > 0) {
List<DetectorNode> inDegreeZeroList = new LinkedList<DetectorNode>();
// Get all of the detectors nodes with in-degree 0.
// These have no unsatisfied prerequisites, and thus can
// be chosen for the current pass.
for (Iterator<DetectorNode> i = constraintGraph.vertexIterator(); i.hasNext();) {
DetectorNode node = i.next();
if (constraintGraph.getNumIncomingEdges(node) == 0) {
inDegreeZeroList.add(node);
} else if (DEBUG ) {
System.out.println("Can't schedule " + node.getFactory().getShortName());
Iterator<ConstraintEdge> incomingEdgeIterator = constraintGraph.incomingEdgeIterator(node);
while (incomingEdgeIterator.hasNext()) {
ConstraintEdge edge = incomingEdgeIterator.next();
System.out.println(" requires " + edge.getSource().getFactory().getShortName());
}
}
}
if (inDegreeZeroList.isEmpty()) {
throw new OrderingConstraintException("Cycle in inter-pass ordering constraints");
}
// Remove all of the chosen detectors from the constraint graph.
for (DetectorNode node : inDegreeZeroList) {
constraintGraph.removeVertex(node);
}
// Create analysis pass and add detector factories.
// Note that this just makes the detectors members of the pass:
// it doesn't assign them a position in the pass.
AnalysisPass pass = new AnalysisPass();
addPass(pass);
passCount++;
for (DetectorNode node : inDegreeZeroList) {
assignToPass(node.getFactory(), pass);
}
}
}
private void addPass(AnalysisPass pass) {
if (DEBUG) {
System.out.println("Adding pass " + passList.size());
}
passList.add(pass);
}
private void sortPass(List<DetectorOrderingConstraint> constraintList, Map<String, DetectorFactory> factoryMap,
AnalysisPass pass) throws OrderingConstraintException {
// Build set of all (initial) detectors in pass
Set<DetectorFactory> detectorSet = new HashSet<DetectorFactory>(pass.getMembers());
if (DEBUG) {
System.out.println(detectorSet.size() + " detectors currently in this pass");
}
// Build list of ordering constraints in this pass only
List<DetectorOrderingConstraint> passConstraintList = new LinkedList<DetectorOrderingConstraint>();
for (DetectorOrderingConstraint constraint : constraintList) {
// Does this constraint specify any detectors in this pass?
// If so, add it to the pass constraints
if (selectDetectors(constraint.getEarlier(), detectorSet).size() > 0
|| selectDetectors(constraint.getLater(), detectorSet).size() > 0) {
passConstraintList.add(constraint);
}
}
if (DEBUG) {
System.out.println(passConstraintList.size() + " constraints are applicable for this pass");
}
// Build set of all detectors available to be added to this pass
HashSet<DetectorFactory> availableSet = new HashSet<DetectorFactory>();
availableSet.addAll(detectorSet);
availableSet.addAll(getUnassignedSet());
// Build intra-pass constraint graph
Map<String, DetectorNode> nodeMap = new HashMap<String, DetectorNode>();
ConstraintGraph constraintGraph = buildConstraintGraph(nodeMap, availableSet, passConstraintList);
if (DEBUG) {
System.out.println("Pass constraint graph:");
dumpGraph(constraintGraph);
}
// See if any detectors were brought into the pass by an intrapass
// ordering constraint.
// Assign them to the pass officially.
for (DetectorNode node : nodeMap.values()) {
if (!pass.contains(node.getFactory())) {
assignToPass(node.getFactory(), pass);
}
}
// Perform DFS, check for cycles
DepthFirstSearch<ConstraintGraph, ConstraintEdge, DetectorNode> dfs = new DepthFirstSearch<ConstraintGraph, ConstraintEdge, DetectorNode>(
constraintGraph);
dfs.search();
if (dfs.containsCycle()) {
throw new OrderingConstraintException("Cycle in intra-pass ordering constraints!");
}
// Do a topological sort to put the detectors in the pass
// in the right order.
for (Iterator<DetectorNode> i = dfs.topologicalSortIterator(); i.hasNext();) {
DetectorNode node = i.next();
appendToPass(node.getFactory(), pass);
}
// Add any detectors not explicitly involved in intra-pass ordering
// constraints
// to the end of the pass.
appendDetectorsToPass(pass.getUnpositionedMembers(), pass);
}
private Set<DetectorFactory> getUnassignedSet() {
Set<DetectorFactory> unassignedSet = new HashSet<DetectorFactory>();
unassignedSet.addAll(factoryMap.values());
unassignedSet.removeAll(assignedToPassSet);
return unassignedSet;
}
/**
* Make a DetectorFactory a member of an AnalysisPass.
*/
private void assignToPass(DetectorFactory factory, AnalysisPass pass) {
pass.addToPass(factory);
assignedToPassSet.add(factory);
}
/**
* Append a DetectorFactory to the end position in an AnalysisPass. The
* DetectorFactory must be a member of the pass.
*/
private void appendToPass(DetectorFactory factory, AnalysisPass pass) {
pass.append(factory);
}
private void appendDetectorsToPass(Collection<DetectorFactory> detectorSet, AnalysisPass pass)
{
DetectorFactory[] unassignedList = detectorSet.toArray(new DetectorFactory[detectorSet.size()]);
Arrays.sort(unassignedList, new Comparator<DetectorFactory>() {
@Override
public int compare(DetectorFactory a, DetectorFactory b) {
// Sort first by plugin id...
int cmp = a.getPlugin().getPluginId().compareTo(b.getPlugin().getPluginId());
if (cmp != 0) {
return cmp;
}
// Then by order specified in plugin descriptor
return a.getPositionSpecifiedInPluginDescriptor() - b.getPositionSpecifiedInPluginDescriptor();
}
});
for (DetectorFactory factory : unassignedList) {
appendToPass(factory, pass);
}
}
private void print() {
System.out.println("\nExecution plan:");
int passCount = 0;
for (Iterator<AnalysisPass> i = passList.iterator(); i.hasNext(); ++passCount) {
System.out.println("Pass " + passCount);
AnalysisPass pass = i.next();
for (Iterator<DetectorFactory> j = pass.iterator(); j.hasNext();) {
DetectorFactory factory = j.next();
System.out.println(" " + factory.getShortName());
}
}
System.out.println();
}
private void dumpGraph(ConstraintGraph graph) {
for (Iterator<ConstraintEdge> i = graph.edgeIterator(); i.hasNext();) {
ConstraintEdge edge = i.next();
System.out.println(edge.getSource().getFactory().getShortName() + " ==> "
+ edge.getTarget().getFactory().getShortName());
}
}
public static void main(String[] argv) throws Exception {
DetectorFactoryCollection detectorFactoryCollection = DetectorFactoryCollection.instance();
ExecutionPlan execPlan = new ExecutionPlan();
for (String pluginId : argv) {
Plugin plugin = detectorFactoryCollection.getPluginById(pluginId);
if (plugin != null) {
execPlan.addPlugin(plugin);
}
}
execPlan.build();
System.out.println(execPlan.getNumPasses() + " passes in plan");
execPlan.print();
}
}