org.codenarc.rule.size.CrapMetricRule.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of CodeNarc Show documentation
Show all versions of CodeNarc Show documentation
The CodeNarc project provides a static analysis tool for Groovy code.
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codenarc.rule.size
import org.apache.log4j.Logger
import org.codenarc.rule.AbstractAstVisitorRule
import org.codenarc.rule.AstVisitor
import org.codenarc.util.io.DefaultResourceFactory
import org.gmetrics.metric.coverage.CoberturaLineCoverageMetric
import org.gmetrics.metric.crap.CrapMetric
/**
* Rule that calculates the CRAP Metric for methods/classes and checks against
* configured threshold values.
*
* Note that this rule requires the GMetrics 0.5 (or later) jar on the classpath, as well as
* a Cobertura XML coverage file. If either of these prerequisites is not available, this rule
* logs a warning messages and exits (i.e., does nothing).
*
* The coberturaXmlFile
property must be set to the path to the Cobertura XML coverage file
* for the Groovy code being analyzed. By default, the path is relative to the classpath. But the path
* may be optionally prefixed by any of the valid java.net.URL prefixes, such as "file:" (to load from
* a relative or absolute path on the filesystem), or "http:". This property is REQUIRED.
*
* The maxMethodCrapScore
property holds the threshold value for the CRAP crapMetric
* value for each method. If this value is non-zero, a method with a CRAP score value greater than
* this value is considered a violation. The maxMethodCrapScore
property defaults to 30.
*
* The maxClassAverageCrapScore
property holds the threshold value for the average CRAP
* crapMetric value for each class. If this value is non-zero, a class with an average CRAP score
* value greater than this value is considered a violation. The maxMethodAverageCrapScore
property
* defaults to 30.
*
* The maxClassCrapScore
property holds the threshold value for the total CRAP
* crapMetric value for each class. If this value is non-zero, a class with a total CRAP score
* value greater than this value is considered a violation. The maxClassCrapScore
property
* defaults to 0.
*
* The ignoreMethodNames
property optionally specifies one or more (comma-separated) method
* names that should be ignored (i.e., that should not cause a rule violation). The name(s) may optionally
* include wildcard characters ('*' or '?'). Note that the ignored methods still contribute to the class
* complexity value.
*
* This rule does NOT treat "closure fields" as methods (unlike some of the other size/complexity rules).
*
* @see The original 2007 blog post that defined the CRAP crapMetric.
* @see A 2011 blog post from Alberto Savoia, describing the formula, the motivation, and the CRAP4J tool.
* @see GMetrics CRAP crapMetric.
*
* @author Chris Mair
*/
class CrapMetricRule extends AbstractAstVisitorRule {
private static final LOG = Logger.getLogger(CrapMetricRule)
String name = 'CrapMetric'
int priority = 2
BigDecimal maxMethodCrapScore = 30
BigDecimal maxClassAverageMethodCrapScore = 30
BigDecimal maxClassCrapScore = 0
String coberturaXmlFile
String ignoreMethodNames
protected String crapMetricClassName = 'org.gmetrics.metric.crap.CrapMetric'
private Boolean ready
private crapMetric // omit CrapMetric type; it may not be on the classpath
private final readyLock = new Object()
private final createMetricLock = new Object()
private final resourceFactory = new DefaultResourceFactory()
@Override
AstVisitor getAstVisitor() {
return new CrapMetricAstVisitor(createCrapMetric())
}
@Override
boolean isReady() {
synchronized(readyLock) {
if (ready == null) {
ready = true
if (!doesCoberturaXmlFileExist()) {
LOG.warn("The Cobertura XML file [$coberturaXmlFile] is not accessible; skipping this rule")
ready = false
}
if (!isCrapMetricClassOnClasspath()) {
LOG.warn('The GMetrics CrapMetric class is not on the classpath; skipping this rule')
ready = false
}
}
}
return ready
}
private boolean doesCoberturaXmlFileExist() {
if (!coberturaXmlFile) {
return false
}
def resource = resourceFactory.getResource(coberturaXmlFile)
return resource.exists()
}
private createCrapMetric() { // omit CrapMetric type; it may not be on the classpath
synchronized(createMetricLock) {
if (!crapMetric) {
def coverageMetric = new CoberturaLineCoverageMetric(coberturaFile:coberturaXmlFile)
crapMetric = new CrapMetric(coverageMetric:coverageMetric)
}
}
return crapMetric
}
private boolean isCrapMetricClassOnClasspath() {
try {
getClass().classLoader.loadClass(crapMetricClassName)
return true
}
catch (ClassNotFoundException e) {
return false
}
}
}
class CrapMetricAstVisitor extends AbstractMethodMetricAstVisitor {
final String metricShortDescription = 'CRAP score'
private final CrapMetric crapMetric
protected CrapMetricAstVisitor(CrapMetric crapMetric) {
this.crapMetric = crapMetric
}
@Override
protected Object createMetric() {
return crapMetric
}
@Override
protected Object getMaxMethodMetricValue() {
rule.maxMethodCrapScore
}
@Override
protected Object getMaxClassAverageMethodMetricValue() {
rule.maxClassAverageMethodCrapScore
}
protected Object getMaxClassMetricValue() {
rule.maxClassCrapScore
}
}