/*
* 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;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import javax.annotation.CheckForNull;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.util.Util;
/**
* Bug rankers are used to compute a bug rank for each bug instance. Bug ranks 1-20 are for bugs that are visible to users.
* Bug rank 1 is more the most relevant/scary bugs. A bug rank greater than 20 is for issues that should not be shown to users.
*
*
* The following bug rankers may exist:
* <ul>
* <li> core bug ranker (loaded from etc/bugrank.txt)
* <li> a bug ranker for each plugin (loaded from <plugin>/etc/bugrank.txt)
* <li> A global adjustment ranker (loaded from plugins/adjustBugrank.txt)
* </ul>
*
* A bug ranker is comprised of a list of bug patterns, bug kinds and bug categories. For each, either an absolute
* or relative bug rank is provided. A relative rank is one preceeded by a + or -.
*
* For core bug detectors, the bug ranker search order is:
* <ul>
* <li> global bug ranker
* <li> core bug ranker
* </ul>
*
* For third party plugins, the bug ranker search order is:
* <ul>
* <li> global adjustment bug ranker
* <li> plugin adjustment bug ranker
* <li> core bug ranker
* </ul>
*
* The overall search order is
* <ul>
* <li> Bug patterns, in search order across bug rankers
* <li> Bug kinds, in search order across bug rankers
* <li> Bug categories, in search order across bug rankers
* </ul>
*
* Search stops at the first absolute bug rank found, and the result is the sum of all of relative bug ranks plus
* the final absolute bug rank. Since all bug categories are defined by the core bug ranker, we should always find
* an absolute bug rank.
*
*
*
* @author Bill Pugh
*/
public class BugRanker {
static class Scorer {
private final HashMap<String, Integer> adjustment = new HashMap<String, Integer>();
private final HashSet<String> isRelative = new HashSet<String>();
int get(String key) {
Integer v = adjustment.get(key);
if (v == null)
return 0;
return v;
}
boolean isRelative(String key) {
return !adjustment.containsKey(key) || isRelative.contains(key);
}
void storeAdjustment(String key, String value) {
int v = Integer.parseInt(value);
char firstChar = value.charAt(0);
adjustment.put(key, v);
if (firstChar == '+' || firstChar == '-')
isRelative.add(key);
}
}
/**
* @param u may be null. In this case, a default value will be used for all bugs
* @throws IOException
*/
public BugRanker(@CheckForNull URL u) throws IOException {
if(u == null){
return;
}
BufferedReader in = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8"));
while (true) {
String s = in.readLine();
if (s == null) break;
s = s.trim();
if (s.length() == 0)
continue;
String parts [] = s.split(" ");
String rank = parts[0];
String kind = parts[1];
String what = parts[2];
if (kind.equals("BugPattern"))
bugPatterns.storeAdjustment(what, rank);
else if (kind.equals("BugKind"))
bugKinds.storeAdjustment(what, rank);
else if (kind.equals("Category"))
bugCategories.storeAdjustment(what, rank);
else
AnalysisContext.logError("Can't parse bug rank " + s);
}
Util.closeSilently(in);
}
private final Scorer bugPatterns = new Scorer();
private final Scorer bugKinds = new Scorer();
private final Scorer bugCategories = new Scorer();
/**
*
*/
public static final String FILENAME = "bugrank.txt";
public static final String ADJUST_FILENAME = "adjustBugrank.txt";
private static int priorityAdjustment(int priority) {
switch (priority) {
case Priorities.HIGH_PRIORITY:
return 0;
case Priorities.NORMAL_PRIORITY:
return 2;
case Priorities.LOW_PRIORITY:
return 5;
default:
return 10;
}
}
public static int rankBug(BugInstance bug, BugRanker... rankers) {
return rankBugPatternWithPriorityAdustment(bug.getBugPattern(), bug.getPriority(), rankers);
}
private static int rankBugPatternWithPriorityAdustment(BugPattern bugPattern, int priority, BugRanker... rankers) {
int rankBugPattern = rankBugPattern(bugPattern, rankers);
int priorityAdjustment = priorityAdjustment(priority);
if (rankBugPattern > 20)
return rankBugPattern + priorityAdjustment;
return Math.min(rankBugPattern + priorityAdjustment, 20);
}
private static int rankBugPattern(BugPattern bugPattern, BugRanker... rankers) {
String type = bugPattern.getType();
int rank = 0;
for(BugRanker b : rankers) if (b != null) {
rank += b.bugPatterns.get(type);
if (!b.bugPatterns.isRelative(type))
return rank;
}
String kind = bugPattern.getAbbrev();
for(BugRanker b : rankers) if (b != null) {
rank += b.bugKinds.get(kind);
if (!b.bugKinds.isRelative(kind))
return rank;
}
String category = bugPattern.getCategory();
for(BugRanker b : rankers) if (b != null) {
rank += b.bugCategories.get(category);
if (!b.bugCategories.isRelative(category))
return rank;
}
return 20;
}
private static BugRanker getAdjustmentBugRanker() {
DetectorFactoryCollection factory = DetectorFactoryCollection.instance();
return factory.getAdjustmentBugRanker();
}
private static BugRanker getCoreRanker() {
DetectorFactoryCollection factory = DetectorFactoryCollection.instance();
return factory.getCorePlugin().getBugRanker();
}
public static int findRank(BugInstance bug) {
DetectorFactory detectorFactory = bug.getDetectorFactory();
if (null == detectorFactory) {
// Unknown detector / plugin (e.g. happens when reading a bug
// collection from its XML representation).
return findRank(bug.getBugPattern(), bug.getPriority());
}
Plugin plugin = detectorFactory.getPlugin();
BugRanker adjustmentRanker = getAdjustmentBugRanker();
BugRanker pluginRanker = plugin.getBugRanker();
BugRanker coreRanker = getCoreRanker();
if (pluginRanker == coreRanker)
return rankBug(bug, adjustmentRanker, coreRanker);
else
return rankBug(bug, adjustmentRanker, pluginRanker, coreRanker);
}
public static int findRank(BugPattern pattern, Plugin plugin, int priority) {
BugRanker adjustmentRanker = getAdjustmentBugRanker();
BugRanker pluginRanker = plugin.getBugRanker();
BugRanker coreRanker = getCoreRanker();
if (pluginRanker == coreRanker)
return rankBugPatternWithPriorityAdustment(pattern, priority, adjustmentRanker, coreRanker);
else
return rankBugPatternWithPriorityAdustment(pattern, priority, adjustmentRanker, pluginRanker, coreRanker);
}
public static int findRank(BugPattern pattern, int priority) {
DetectorFactoryCollection factory = DetectorFactoryCollection.instance();
Plugin corePlugin = factory.getCorePlugin();
List<BugRanker> rankers = new ArrayList<BugRanker>();
rankers.add(getAdjustmentBugRanker());
for (Plugin plugin : factory.plugins()) {
if (plugin != corePlugin) {
rankers.add(plugin.getBugRanker());
}
}
rankers.add(getCoreRanker());
return rankBugPatternWithPriorityAdustment(pattern, priority, rankers.toArray(new BugRanker[] {}));
}
public static void trimToMaxRank(BugCollection origCollection, int maxRank) {
if (maxRank < 20)
for(Iterator<BugInstance> i = origCollection.getCollection().iterator(); i.hasNext(); ) {
BugInstance b = i.next();
if (BugRanker.findRank(b) > maxRank)
i.remove();
}
}
}