Java language support module for the PMD static code analyzer with AST processing, symbol resolution, type system, metrics, and 400+ built-in rules
—
PMD Java provides a comprehensive metrics system with 12 built-in metrics for measuring code quality, complexity, and maintainability. These metrics follow established standards and can be configured with various options.
The metrics system is built on the Metric interface and can be used to calculate various code quality measures.
/**
* Built-in Java metrics for code quality analysis
*/
public final class JavaMetrics {
// Complexity metrics
public static final Metric<ASTExecutableDeclaration, Integer> CYCLO;
public static final Metric<ASTExecutableDeclaration, Integer> COGNITIVE_COMPLEXITY;
public static final Metric<ASTExecutableDeclaration, BigInteger> NPATH;
// Size metrics
public static final Metric<JavaNode, Integer> LINES_OF_CODE;
public static final Metric<JavaNode, Integer> NCSS;
// Class metrics
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_ACCESSORS;
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_PUBLIC_FIELDS;
public static final Metric<ASTTypeDeclaration, Double> TIGHT_CLASS_COHESION;
public static final Metric<ASTTypeDeclaration, Integer> WEIGHED_METHOD_COUNT;
public static final Metric<ASTTypeDeclaration, Double> WEIGHT_OF_CLASS;
// Coupling metrics
public static final Metric<JavaNode, Integer> ACCESS_TO_FOREIGN_DATA;
public static final Metric<JavaNode, Integer> FAN_OUT;
}
/**
* Generic metric interface
*/
public interface Metric<T, R> {
/**
* Compute the metric value for the given node with options
*/
R computeFor(T node, MetricOptions options);
/**
* Get the display name of this metric
*/
String getDisplayName();
/**
* Get the abbreviated name of this metric
*/
String getAbbreviatedName();
}
/**
* Options for configuring metric calculations
*/
public interface MetricOptions {
/**
* Check if a specific option is enabled
*/
boolean isEnabled(MetricOption option);
}Measures the number of independent paths through a block of code using McCabe's original definition.
/**
* Cyclomatic Complexity metric
* Counts independent paths through code
*/
public static final Metric<ASTExecutableDeclaration, Integer> CYCLO;
/**
* Options for cyclomatic complexity calculation
*/
public enum CycloOption implements MetricOption {
/** Do not count the paths in boolean expressions as decision points */
IGNORE_BOOLEAN_PATHS("ignoreBooleanPaths"),
/** Consider assert statements as if they were if (..) throw new AssertionError(..) */
CONSIDER_ASSERT("considerAssert");
String valueName();
}Usage Example:
// Calculate cyclomatic complexity for a method
ASTMethodDeclaration method = /* ... */;
int complexity = JavaMetrics.CYCLO.computeFor(method, MetricOptions.emptyOptions());
// With options to ignore boolean paths
MetricOptions options = MetricOptions.ofOption(CycloOption.IGNORE_BOOLEAN_PATHS);
int simpleComplexity = JavaMetrics.CYCLO.computeFor(method, options);Measures how difficult it is for humans to read and understand a method, with emphasis on nesting.
/**
* Cognitive Complexity metric
* Measures human readability complexity with nesting emphasis
*/
public static final Metric<ASTExecutableDeclaration, Integer> COGNITIVE_COMPLEXITY;Usage Example:
// Calculate cognitive complexity
ASTMethodDeclaration method = /* ... */;
int cognitiveComplexity = JavaMetrics.COGNITIVE_COMPLEXITY.computeFor(method, MetricOptions.emptyOptions());Counts the number of acyclic execution paths through a piece of code.
/**
* NPath Complexity metric
* Counts acyclic execution paths (exponential growth with sequential decisions)
*/
public static final Metric<ASTExecutableDeclaration, BigInteger> NPATH;Usage Example:
// Calculate NPath complexity
ASTMethodDeclaration method = /* ... */;
BigInteger npathComplexity = JavaMetrics.NPATH.computeFor(method, MetricOptions.emptyOptions());
// Check if complexity is over threshold
if (npathComplexity.compareTo(BigInteger.valueOf(200)) > 0) {
System.out.println("Method is too complex: " + npathComplexity);
}Simply counts the number of lines of code including comments and blank lines.
/**
* Lines of Code metric
* Counts total lines including comments and blank lines
*/
public static final Metric<JavaNode, Integer> LINES_OF_CODE;Counts the number of statements, roughly equivalent to counting semicolons and opening braces.
/**
* Non-Commenting Source Statements metric
* Counts actual statements excluding comments and blank lines
*/
public static final Metric<JavaNode, Integer> NCSS;
/**
* Options for NCSS calculation
*/
public enum NcssOption implements MetricOption {
/** Counts import and package statements (makes metric JavaNCSS compliant) */
COUNT_IMPORTS("countImports");
String valueName();
}Usage Example:
// Calculate NCSS for a class
ASTClassDeclaration clazz = /* ... */;
int statements = JavaMetrics.NCSS.computeFor(clazz, MetricOptions.emptyOptions());
// With imports counted
MetricOptions options = MetricOptions.ofOption(NcssOption.COUNT_IMPORTS);
int statementsWithImports = JavaMetrics.NCSS.computeFor(clazz, options);Counts usages of foreign attributes (not belonging to the current class).
/**
* Access to Foreign Data metric
* Counts usages of foreign attributes through direct access or accessors
*/
public static final Metric<JavaNode, Integer> ACCESS_TO_FOREIGN_DATA;Counts the number of other classes a given class or operation relies on.
/**
* Class Fan-Out metric
* Counts dependencies on other classes (excludes java.lang by default)
*/
public static final Metric<JavaNode, Integer> FAN_OUT;
/**
* Options for fan-out calculation
*/
public enum ClassFanOutOption implements MetricOption {
/** Whether to include classes in the java.lang package */
INCLUDE_JAVA_LANG("includeJavaLang");
String valueName();
}Usage Example:
// Calculate fan-out excluding java.lang
ASTClassDeclaration clazz = /* ... */;
int fanOut = JavaMetrics.FAN_OUT.computeFor(clazz, MetricOptions.emptyOptions());
// Include java.lang dependencies
MetricOptions options = MetricOptions.ofOption(ClassFanOutOption.INCLUDE_JAVA_LANG);
int totalFanOut = JavaMetrics.FAN_OUT.computeFor(clazz, options);Sum of the statistical complexity of the operations in the class using CYCLO.
/**
* Weighed Method Count metric
* Sum of cyclomatic complexity of all methods in a class
*/
public static final Metric<ASTTypeDeclaration, Integer> WEIGHED_METHOD_COUNT;Measures the relative number of method pairs that access common attributes.
/**
* Tight Class Cohesion metric
* Measures method pairs that access common attributes (0.0 to 1.0)
*/
public static final Metric<ASTTypeDeclaration, Double> TIGHT_CLASS_COHESION;Usage Example:
// Calculate class cohesion
ASTClassDeclaration clazz = /* ... */;
double cohesion = JavaMetrics.TIGHT_CLASS_COHESION.computeFor(clazz, MetricOptions.emptyOptions());
if (cohesion > 0.7) {
System.out.println("High cohesion class");
} else if (cohesion < 0.5) {
System.out.println("Low cohesion class - consider refactoring");
}Ratio of "functional" public methods to total public methods.
/**
* Weight of Class metric
* Ratio of functional methods to total public methods (0.0 to 1.0)
*/
public static final Metric<ASTTypeDeclaration, Double> WEIGHT_OF_CLASS;Counts getter and setter methods in a class.
/**
* Number of Accessor Methods metric
* Counts getter and setter methods
*/
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_ACCESSORS;Counts public fields in a class.
/**
* Number of Public Attributes metric
* Counts public fields
*/
public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_PUBLIC_FIELDS;import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
import net.sourceforge.pmd.lang.metrics.MetricOptions;
// Calculate complexity for a method
public void analyzeMethod(ASTMethodDeclaration method) {
// Cyclomatic complexity
int cyclo = JavaMetrics.CYCLO.computeFor(method, MetricOptions.emptyOptions());
// Cognitive complexity
int cognitive = JavaMetrics.COGNITIVE_COMPLEXITY.computeFor(method, MetricOptions.emptyOptions());
// NPath complexity
BigInteger npath = JavaMetrics.NPATH.computeFor(method, MetricOptions.emptyOptions());
// Lines of code
int loc = JavaMetrics.LINES_OF_CODE.computeFor(method, MetricOptions.emptyOptions());
System.out.println("Method: " + method.getName());
System.out.println(" Cyclomatic Complexity: " + cyclo);
System.out.println(" Cognitive Complexity: " + cognitive);
System.out.println(" NPath Complexity: " + npath);
System.out.println(" Lines of Code: " + loc);
}// Comprehensive class analysis
public void analyzeClass(ASTClassDeclaration clazz) {
MetricOptions emptyOptions = MetricOptions.emptyOptions();
// Size metrics
int loc = JavaMetrics.LINES_OF_CODE.computeFor(clazz, emptyOptions);
int ncss = JavaMetrics.NCSS.computeFor(clazz, emptyOptions);
// Coupling metrics
int atfd = JavaMetrics.ACCESS_TO_FOREIGN_DATA.computeFor(clazz, emptyOptions);
int fanOut = JavaMetrics.FAN_OUT.computeFor(clazz, emptyOptions);
// Object-oriented metrics
int wmc = JavaMetrics.WEIGHED_METHOD_COUNT.computeFor(clazz, emptyOptions);
double tcc = JavaMetrics.TIGHT_CLASS_COHESION.computeFor(clazz, emptyOptions);
double woc = JavaMetrics.WEIGHT_OF_CLASS.computeFor(clazz, emptyOptions);
// Method and field counts
int accessors = JavaMetrics.NUMBER_OF_ACCESSORS.computeFor(clazz, emptyOptions);
int publicFields = JavaMetrics.NUMBER_OF_PUBLIC_FIELDS.computeFor(clazz, emptyOptions);
System.out.println("Class: " + clazz.getSimpleName());
System.out.println("Size Metrics:");
System.out.println(" Lines of Code: " + loc);
System.out.println(" NCSS: " + ncss);
System.out.println("Coupling Metrics:");
System.out.println(" ATFD: " + atfd);
System.out.println(" Fan-Out: " + fanOut);
System.out.println("OO Metrics:");
System.out.println(" WMC: " + wmc);
System.out.println(" TCC: " + String.format("%.2f", tcc));
System.out.println(" WOC: " + String.format("%.2f", woc));
System.out.println("Counts:");
System.out.println(" Accessors: " + accessors);
System.out.println(" Public Fields: " + publicFields);
}// Using metric options for customized calculations
public void analyzeWithOptions(JavaNode node) {
// Cyclomatic complexity ignoring boolean paths
MetricOptions cycloOptions = MetricOptions.ofOption(
JavaMetrics.CycloOption.IGNORE_BOOLEAN_PATHS,
JavaMetrics.CycloOption.CONSIDER_ASSERT
);
if (node instanceof ASTExecutableDeclaration) {
ASTExecutableDeclaration method = (ASTExecutableDeclaration) node;
int cyclo = JavaMetrics.CYCLO.computeFor(method, cycloOptions);
System.out.println("Simplified Cyclomatic Complexity: " + cyclo);
}
// NCSS including imports
MetricOptions ncssOptions = MetricOptions.ofOption(JavaMetrics.NcssOption.COUNT_IMPORTS);
int ncss = JavaMetrics.NCSS.computeFor(node, ncssOptions);
System.out.println("NCSS with imports: " + ncss);
// Fan-out including java.lang
MetricOptions fanOutOptions = MetricOptions.ofOption(JavaMetrics.ClassFanOutOption.INCLUDE_JAVA_LANG);
int fanOut = JavaMetrics.FAN_OUT.computeFor(node, fanOutOptions);
System.out.println("Total Fan-Out: " + fanOut);
}// Visitor to collect metrics across an entire compilation unit
public class MetricsCollector extends JavaVisitorBase<Void, Void> {
private List<ClassMetrics> classMetrics = new ArrayList<>();
private List<MethodMetrics> methodMetrics = new ArrayList<>();
@Override
public Void visit(ASTClassDeclaration node, Void data) {
// Collect class-level metrics
ClassMetrics metrics = new ClassMetrics();
metrics.className = node.getSimpleName();
metrics.loc = JavaMetrics.LINES_OF_CODE.computeFor(node, MetricOptions.emptyOptions());
metrics.ncss = JavaMetrics.NCSS.computeFor(node, MetricOptions.emptyOptions());
metrics.wmc = JavaMetrics.WEIGHED_METHOD_COUNT.computeFor(node, MetricOptions.emptyOptions());
metrics.tcc = JavaMetrics.TIGHT_CLASS_COHESION.computeFor(node, MetricOptions.emptyOptions());
classMetrics.add(metrics);
return super.visit(node, data);
}
@Override
public Void visit(ASTMethodDeclaration node, Void data) {
// Collect method-level metrics
MethodMetrics metrics = new MethodMetrics();
metrics.methodName = node.getName();
metrics.cyclo = JavaMetrics.CYCLO.computeFor(node, MetricOptions.emptyOptions());
metrics.cognitive = JavaMetrics.COGNITIVE_COMPLEXITY.computeFor(node, MetricOptions.emptyOptions());
metrics.npath = JavaMetrics.NPATH.computeFor(node, MetricOptions.emptyOptions());
metrics.loc = JavaMetrics.LINES_OF_CODE.computeFor(node, MetricOptions.emptyOptions());
methodMetrics.add(metrics);
return super.visit(node, data);
}
public List<ClassMetrics> getClassMetrics() { return classMetrics; }
public List<MethodMetrics> getMethodMetrics() { return methodMetrics; }
static class ClassMetrics {
String className;
int loc, ncss, wmc;
double tcc;
}
static class MethodMetrics {
String methodName;
int cyclo, cognitive, loc;
BigInteger npath;
}
}70%: High cohesion, single responsibility
70%: Good behavioral interface
Install with Tessl CLI
npx tessl i tessl/maven-net-sourceforge-pmd--pmd-java