/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.rule.stat; import static net.sourceforge.pmd.lang.rule.stat.StatisticalRule.MINIMUM_DESCRIPTOR; import static net.sourceforge.pmd.lang.rule.stat.StatisticalRule.SIGMA_DESCRIPTOR; import static net.sourceforge.pmd.lang.rule.stat.StatisticalRule.TOP_SCORE_DESCRIPTOR; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.rule.AbstractRule; import net.sourceforge.pmd.stat.DataPoint; import net.sourceforge.pmd.stat.Metric; /** * This class is used to implement the core logic of a StatisticalRule. Concrete * Rule implementations should delegate to an instance of this class. * * @author David Dixon-Peugh Aug 8, 2002 StatisticalRule.java */ public class StatisticalRuleHelper { public static final double DELTA = 0.000005; // Within this range. . . private AbstractRule rule; private SortedSet<DataPoint> dataPoints = new TreeSet<>(); private int count = 0; private double total = 0.0; public StatisticalRuleHelper(AbstractRule rule) { this.rule = rule; rule.definePropertyDescriptor(SIGMA_DESCRIPTOR); rule.definePropertyDescriptor(MINIMUM_DESCRIPTOR); rule.definePropertyDescriptor(TOP_SCORE_DESCRIPTOR); } public void addDataPoint(DataPoint point) { count++; total += point.getScore(); dataPoints.add(point); } public void apply(RuleContext ctx) { double deviation; double minimum = 0.0; if (rule.getProperty(SIGMA_DESCRIPTOR) != null) { // TODO - need to come // up with a good // default value deviation = getStdDev(); double sigma = rule.getProperty(SIGMA_DESCRIPTOR); minimum = getMean() + (sigma * deviation); } if (rule.getProperty(MINIMUM_DESCRIPTOR) != null) { // TODO - need to // come up with a // good default // value double mMin = rule.getProperty(MINIMUM_DESCRIPTOR); if (mMin > minimum) { minimum = mMin; } } SortedSet<DataPoint> newPoints = applyMinimumValue(dataPoints, minimum); if (rule.getProperty(TOP_SCORE_DESCRIPTOR) != null) { // TODO - need to // come up with a // good default // value int topScore = rule.getProperty(TOP_SCORE_DESCRIPTOR); if (newPoints.size() >= topScore) { newPoints = applyTopScore(newPoints, topScore); } } makeViolations(ctx, newPoints); double low = 0.0d; double high = 0.0d; if (!dataPoints.isEmpty()) { low = dataPoints.first().getScore(); high = dataPoints.last().getScore(); } ctx.getReport().addMetric(new Metric(rule.getName(), count, total, low, high, getMean(), getStdDev())); dataPoints.clear(); } private double getMean() { return total / count; } private double getStdDev() { if (dataPoints.size() < 2) { return Double.NaN; } double mean = getMean(); double deltaSq = 0.0; double scoreMinusMean; for (DataPoint point : dataPoints) { scoreMinusMean = point.getScore() - mean; deltaSq += scoreMinusMean * scoreMinusMean; } return Math.sqrt(deltaSq / (dataPoints.size() - 1)); } private SortedSet<DataPoint> applyMinimumValue(SortedSet<DataPoint> pointSet, double minValue) { SortedSet<DataPoint> rc = new TreeSet<>(); double threshold = minValue - DELTA; for (DataPoint point : pointSet) { if (point.getScore() > threshold) { rc.add(point); } } return rc; } private SortedSet<DataPoint> applyTopScore(SortedSet<DataPoint> points, int topScore) { SortedSet<DataPoint> s = new TreeSet<>(); DataPoint[] arr = points.toArray(new DataPoint[] {}); for (int i = arr.length - 1; i >= (arr.length - topScore); i--) { s.add(arr[i]); } return s; } private void makeViolations(RuleContext ctx, Set<DataPoint> p) { for (DataPoint point : p) { rule.addViolationWithMessage(ctx, point.getNode(), point.getMessage(), ((StatisticalRule) rule).getViolationParameters(point)); } } }