• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 353

16 Jan 2026 01:01PM UTC coverage: 78.957% (-0.02%) from 78.976%
353

push

github

adangel
[core] Fix #6184: More consistent enum properties (#6233)

18529 of 24342 branches covered (76.12%)

Branch coverage included in aggregate %.

146 of 164 new or added lines in 20 files covered. (89.02%)

6 existing lines in 5 files now uncovered.

40349 of 50228 relevant lines covered (80.33%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.71
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.metrics;
6

7
import static net.sourceforge.pmd.internal.util.PredicateUtil.always;
8

9
import java.math.BigInteger;
10
import java.util.ArrayList;
11
import java.util.Collections;
12
import java.util.HashSet;
13
import java.util.List;
14
import java.util.Set;
15
import java.util.function.Function;
16
import java.util.function.Predicate;
17

18
import org.apache.commons.lang3.mutable.MutableInt;
19
import org.checkerframework.checker.nullness.qual.Nullable;
20

21
import net.sourceforge.pmd.lang.ast.Node;
22
import net.sourceforge.pmd.lang.ast.NodeStream;
23
import net.sourceforge.pmd.lang.document.FileLocation;
24
import net.sourceforge.pmd.lang.java.ast.ASTAssignableExpr.ASTNamedReferenceExpr;
25
import net.sourceforge.pmd.lang.java.ast.ASTExecutableDeclaration;
26
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
27
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
28
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
29
import net.sourceforge.pmd.lang.java.ast.JavaNode;
30
import net.sourceforge.pmd.lang.java.ast.ModifierOwner;
31
import net.sourceforge.pmd.lang.java.ast.ReturnScopeNode;
32
import net.sourceforge.pmd.lang.java.metrics.internal.AtfdBaseVisitor;
33
import net.sourceforge.pmd.lang.java.metrics.internal.ClassFanOutVisitor;
34
import net.sourceforge.pmd.lang.java.metrics.internal.CognitiveComplexityVisitor;
35
import net.sourceforge.pmd.lang.java.metrics.internal.CognitiveComplexityVisitor.State;
36
import net.sourceforge.pmd.lang.java.metrics.internal.CycloVisitor;
37
import net.sourceforge.pmd.lang.java.metrics.internal.NPathMetricCalculator;
38
import net.sourceforge.pmd.lang.java.metrics.internal.NcssVisitor;
39
import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil;
40
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
41
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
42
import net.sourceforge.pmd.lang.java.symbols.JVariableSymbol;
43
import net.sourceforge.pmd.lang.metrics.Metric;
44
import net.sourceforge.pmd.lang.metrics.MetricOption;
45
import net.sourceforge.pmd.lang.metrics.MetricOptions;
46
import net.sourceforge.pmd.lang.metrics.MetricsUtil;
47

48
/**
49
 * Built-in Java metrics. See {@link Metric} and {@link MetricsUtil}
50
 * for usage doc.
51
 *
52
 * @see "Michele Lanza and Radu Marinescu. <i>Object-Oriented Metrics in Practice:
53
 * Using Software Metrics to Characterize, Evaluate, and Improve the Design
54
 * of Object-Oriented Systems.</i> Springer, Berlin, 1 edition, October 2006."
55
 */
56
public final class JavaMetrics {
57

58
    /**
59
     * Number of usages of foreign attributes, both directly and through accessors.
60
     * "Foreign" hier means "not belonging to {@code this}", although field accesses
61
     * to fields declared in the enclosing class are not considered foreign.
62
     *
63
     * <p>High values of ATFD (&gt; 3 for an operation) may suggest that the
64
     * class or operation breaks encapsulation by relying on the internal
65
     * representation of the classes it uses instead of the services they provide.
66
     */
67
    public static final Metric<JavaNode, Integer> ACCESS_TO_FOREIGN_DATA =
1✔
68
        Metric.of(JavaMetrics::computeAtfd, isJavaNode(),
1✔
69
                  "Access To Foreign Data", "ATFD");
70

71

72
    /**
73
     * Number of independent paths through a block of code.
74
     * Formally, given that the control flow graph of the block has n
75
     * vertices, e edges and p connected components, the cyclomatic complexity
76
     * of the block is given by {@code CYCLO = e - n + 2p}. In practice
77
     * it can be calculated by counting control flow statements following
78
     * the standard rules given below.
79
     *
80
     * <p>The standard version of the metric complies with McCabe’s original definition:
81
     * <ul>
82
     *  <li>Methods have a base complexity of 1.
83
     *  <li>+1 for every control flow statement (if, case, catch, throw,
84
     *  do, while, for, break, continue) and conditional expression (?:).
85
     *  Notice switch cases count as one, but not the switch itself: the
86
     *  point is that a switch should have the same complexity value as
87
     *  the equivalent series of if statements.
88
     *  <li>else, finally and default do not count;
89
     *  <li>+1 for every boolean operator ({@code &&}, {@code ||}) in
90
     *  the guard condition of a control flow statement. That’s because
91
     *  Java has short-circuit evaluation semantics for boolean operators,
92
     *  which makes every boolean operator kind of a control flow statement in itself.
93
     * </ul>
94
     *
95
     * <p>Code example:
96
     * <pre>{@code
97
     * class Foo {
98
     *   void baseCyclo() {                // Cyclo = 1
99
     *     highCyclo();
100
     *   }
101
     *
102
     *   void highCyclo() {                // Cyclo = 10
103
     *     int x = 0, y = 2;
104
     *     boolean a = false, b = true;
105
     *
106
     *     if (a && (y == 1 ? b : true)) { // +3
107
     *       if (y == x) {                 // +1
108
     *         while (true) {              // +1
109
     *           if (x++ < 20) {           // +1
110
     *             break;                  // +1
111
     *           }
112
     *         }
113
     *       } else if (y == t && !d) {    // +2
114
     *         x = a ? y : x;              // +1
115
     *       } else {
116
     *         x = 2;
117
     *       }
118
     *     }
119
     *   }
120
     * }
121
     * }</pre>
122
     *
123
     * @see CycloOption
124
     */
125
    public static final Metric<ASTExecutableDeclaration, Integer> CYCLO =
1✔
126
        Metric.of(JavaMetrics::computeCyclo, asMethodOrCtor(),
1✔
127
                  "Cyclomatic Complexity", "Cyclo");
128

129
    /**
130
     * Cognitive complexity is a measure of how difficult it is for humans
131
     * to read and understand a method. Code that contains a break in the
132
     * control flow is more complex, whereas the use of language shorthands
133
     * doesn't increase the level of complexity. Nested control flows can
134
     * make a method more difficult to understand, with each additional
135
     * nesting of the control flow leading to an increase in cognitive
136
     * complexity.
137
     *
138
     * <p>Information about Cognitive complexity can be found in the original paper here:
139
     * <a href='https://www.sonarsource.com/docs/CognitiveComplexity.pdf'>CognitiveComplexity</a>
140
     *
141
     * <p><strong>Basic Idea</strong></p>
142
     * <ol>
143
     * <li>Ignore structures that allow multiple statements to be readably shorthanded into one
144
     * <li>Increment (add one) for each break in the linear flow of the code
145
     * <li>Increment when flow-breaking structures are nested
146
     * </ol>
147
     *
148
     * <p><strong>Increments</strong></p>
149
     * There is an increment for each of the following:
150
     * <ul>
151
     * <li>`if`, `else if`, `else`, ternary operator
152
     * <li>`switch`
153
     * <li>`for`, `foreach`
154
     * <li>`while`, `do while`
155
     * <li>`catch`
156
     * <li>`goto LABEL`, `break LABEL`, `continue LABEL`
157
     * <li>sequences of binary logical operators
158
     * <li>each method in a recursion cycle
159
     * </ul>
160
     *
161
     * <p><strong>Nesting level</strong></p>
162
     * The following structures increment the nesting level:
163
     * <ul>
164
     * <li>`if`, `else if`, `else`, ternary operator
165
     * <li>`switch`
166
     * <li>`for`, `foreach`
167
     * <li>`while`, `do while`
168
     * <li>`catch`
169
     * <li>nested methods and method-like structures such as lambdas
170
     * </ul>
171
     *
172
     * <p><strong>Nesting increments</strong></p>
173
     * The following structures receive a nesting increment commensurate with their nested depth
174
     * inside nested structures:
175
     * <ul>
176
     * <li>`if`, ternary operator
177
     * <li>`switch`
178
     * <li>`for`, `foreach`
179
     * <li>`while`, `do while`
180
     * <li>`catch`
181
     * </ul>
182
     *
183
     * <p><strong>Code example</strong></p>
184
     * <pre>{@code
185
     * class Foo {
186
     *   void myMethod () {
187
     *     try {
188
     *       if (condition1) { // +1
189
     *         for (int i = 0; i < 10; i++) { // +2 (nesting=1)
190
     *           while (condition2) { } // +3 (nesting=2)
191
     *         }
192
     *       }
193
     *     } catch (ExcepType1 | ExcepType2 e) { // +1
194
     *       if (condition2) { } // +2 (nesting=1)
195
     *     }
196
     *   } // Cognitive Complexity 9
197
     * }
198
     * }</pre>
199
     */
200
    public static final Metric<ASTExecutableDeclaration, Integer> COGNITIVE_COMPLEXITY =
1✔
201
        Metric.of(JavaMetrics::computeCognitive, asMethodOrCtor(),
1✔
202
                  "Cognitive Complexity", "CognitiveComp");
203

204
    /**
205
     * This counts the number of other classes a given class or operation
206
     * relies on. Classes from the package {@code java.lang} are ignored
207
     * by default (can be changed via options). Also primitives are not
208
     * included into the count.
209
     *
210
     * <p>Code example:
211
     * <pre>{@code
212
     * import java.util.*;
213
     * import java.io.IOException;
214
     *
215
     * public class Foo { // total 8
216
     *     public Set set = new HashSet(); // +2
217
     *     public Map map = new HashMap(); // +2
218
     *     public String string = ""; // from java.lang -> does not count by default
219
     *     public Double number = 0.0; // from java.lang -> does not count by default
220
     *     public int[] intArray = new int[3]; // primitive -> does not count
221
     *
222
     *     \@Deprecated // from java.lang -> does not count by default
223
     *     public void foo(List list) throws Exception { // +1 (Exception is from java.lang)
224
     *         throw new IOException(); // +1
225
     *     }
226
     *
227
     *     public int getMapSize() {
228
     *         return map.size(); // +1 because it uses the Class from the 'map' field
229
     *     }
230
     * }
231
     * }</pre>
232
     *
233
     * @see ClassFanOutOption
234
     */
235
    public static final Metric<JavaNode, Integer> FAN_OUT =
1✔
236
        Metric.of(JavaMetrics::computeFanOut, isJavaNode(),
1✔
237
                  "Fan-Out", "CFO");
238

239
    /**
240
     * Simply counts the number of lines of code the operation or class
241
     * takes up in the source. This metric doesn’t discount comments or blank lines.
242
     * See {@link #NCSS} for a less biased metric.
243
     */
244
    public static final Metric<JavaNode, Integer> LINES_OF_CODE =
1✔
245
        Metric.of(JavaMetrics::computeLoc, isJavaNode(),
1✔
246
                  "Lines of code", "LOC");
247

248
    /**
249
     * Number of statements in a class or operation. That’s roughly
250
     * equivalent to counting the number of semicolons and opening braces
251
     * in the program. Comments and blank lines are ignored, and statements
252
     * spread on multiple lines count as only one (e.g. {@code int\n a;}
253
     * counts a single statement).
254
     *
255
     * <p>The standard version of the metric is based off [JavaNCSS](http://www.kclee.de/clemens/java/javancss/):
256
     * <ul>
257
     * <li>+1 for any of the following statements: {@code if}, {@code else},
258
     * {@code while}, {@code do}, {@code for}, {@code switch}, {@code break},
259
     * {@code continue}, {@code return}, {@code throw}, {@code synchronized},
260
     * {@code catch}, {@code finally}.
261
     * <li>+1 for each assignment, variable declaration (except for loop initializers)
262
     * or statement expression. We count variables declared on the same line (e.g.
263
     * {@code int a, b, c;}) as a single statement.
264
     * <li>Contrary to Sonarqube, but as JavaNCSS, we count type declarations (class, interface, enum, annotation),
265
     * and method and field declarations.
266
     * <li>Contrary to JavaNCSS, but as Sonarqube, we do not count package
267
     * declaration and import declarations as statements. This makes it
268
     * easier to compare nested classes to outer classes. Besides, it makes
269
     * for class metric results that actually represent the size of the class
270
     * and not of the file. If you don’t like that behaviour, use the {@link NcssOption#COUNT_IMPORTS} option.
271
     * </ul>
272
     *
273
     * <pre>{@code
274
     * import java.util.Collections;       // +0
275
     * import java.io.IOException;         // +0
276
     *
277
     * class Foo {                         // +1, total Ncss = 12
278
     *
279
     *   public void bigMethod()           // +1
280
     *       throws IOException {
281
     *     int x = 0, y = 2;               // +1
282
     *     boolean a = false, b = true;    // +1
283
     *
284
     *     if (a || b) {                   // +1
285
     *       try {
286
     *         do {                        // +1
287
     *           x += 2;                   // +1
288
     *         } while (x < 12);
289
     *
290
     *         System.exit(0);             // +1
291
     *       } catch (IOException ioe) {   // +1
292
     *         throw new PatheticFailException(ioe); // +1
293
     *       }
294
     *     } else {                        // +1
295
     *       assert false;                 // +1
296
     *     }
297
     *   }
298
     * }
299
     * }</pre>
300
     */
301
    public static final Metric<JavaNode, Integer> NCSS =
1✔
302
        Metric.of(JavaMetrics::computeNcss, isJavaNode(),
1✔
303
                  "Non-commenting source statements", "NCSS");
304

305
    /**
306
     * Number of acyclic execution paths through a piece of code. This
307
     * is related to cyclomatic complexity, but the two metrics don’t
308
     * count the same thing: NPath counts the number of distinct full
309
     * paths from the beginning to the end of the method, while Cyclo
310
     * only counts the number of decision points. NPath is not computed
311
     * as simply as {@link #CYCLO}. With NPath, two decision points appearing
312
     * sequentially have their complexity multiplied.
313
     *
314
     * <p>The fact that NPath multiplies the complexity of statements makes
315
     * it grow exponentially: 10 {@code if .. else} statements in a row
316
     * would give an NPath of 1024, while Cyclo would evaluate to 20.
317
     * Methods with an NPath complexity over 200 are generally considered
318
     * too complex.
319
     *
320
     * <p>NPath is computed through control flow analysis. We walk a block
321
     * and keep track of how many control flow paths lead to the current
322
     * program point. We make sure to separate paths that end abruptly
323
     * (for instance because of a throw, or return). This accurately counts
324
     * the number of paths that lead out of a given method. For instance:
325
     * <pre>{@code
326
     *  // entry
327
     *  if (foo)
328
     *     return foo;
329
     *  doSomething();
330
     *  // exit
331
     * }</pre>
332
     * Here there are two paths from the entry to the exit of the method:
333
     * one that ends after doSomething(), and another that ends with the
334
     * return statement. Complexity can snowball rapidly. For instance:
335
     * <pre>{@code
336
     *  // entry
337
     *  if (foo) a = 1; else a = 2;
338
     *  // join
339
     *  if (bar) b = 3; else b = 4;
340
     *  // exit
341
     * }</pre>
342
     * Here there are two paths from {@code entry} to {@code join}: one
343
     * that goes through each branch of the if/else. Then there are two
344
     * paths from {@code join} to {@code exit}, for the same reason. The
345
     * total number of paths from {@code entry} to {@code exit} is
346
     * {@code 2 * 2 = 4}. Since complexities multiply in this way, it
347
     * can grow exponentially. This is not the case with early-return
348
     * patterns for instance:
349
     * <pre>{@code
350
     *  // entry
351
     *  if (foo) return x;
352
     *  // join
353
     *  if (bar) = 3;
354
     *  // exit
355
     * }</pre>
356
     * Here there is only one path from {@code entry} to {@code join},
357
     * because the {@code if} branch returns early. There are two paths
358
     * from {@code join} to {@code exit} (and notice, that's true even if
359
     * there is no else branch, because the path where the if condition is
360
     * false must be taken into account anyway). So in total there are {@code 1*2 + 1 = 3}
361
     * paths from {@code entry} to the end of the block or function (the
362
     * return statement still counts).
363
     *
364
     * <p>Note that shortcut boolean operators are counted as control flow
365
     * branches, especially if they happen in the condition of a control flow
366
     * statement. For instance
367
     * <pre>{@code
368
     *  // entry
369
     *  if (foo || bar)
370
     *     return foo ? a() : b();
371
     *  doSomething();
372
     *  // exit
373
     * }</pre>
374
     * How many paths are there here? There is one path that goes from {@code entry}
375
     * to {@code exit}, which is taken if {@code !foo && !bar}. There is
376
     * one path that leads to the return statement if foo is true, and
377
     * another if foo is false and bar is true. In the return statement,
378
     * there is one path that executes a() and another that executes b().
379
     * In total, there are 2 * 2 paths that start at {@code entry} and
380
     * end at the return statement, and 1 path that goes from {@code entry}
381
     * to {@code exit}, so the total is 5 paths.
382
     */
383
    public static final Metric<ReturnScopeNode, Long> NPATH_COMP =
1✔
384
            Metric.of(JavaMetrics::computeNpath, NodeStream.asInstanceOf(ReturnScopeNode.class),
1✔
385
                    "NPath Complexity", "NPath");
386

387
    /**
388
     * @deprecated Since 7.14.0. Use {@link #NPATH_COMP}, which is available on more nodes,
389
     *             and uses Long instead of BigInteger.
390
     */
391
    @Deprecated
392
    public static final Metric<ASTExecutableDeclaration, BigInteger> NPATH =
1✔
393
        Metric.of(JavaMetrics::computeNpath, asMethodOrCtor(),
1✔
394
                  "NPath Complexity (deprecated)", "NPath");
395

396
    public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_ACCESSORS =
1✔
397
        Metric.of(JavaMetrics::computeNoam, asClass(always()),
1✔
398
                  "Number of accessor methods", "NOAM");
399

400
    public static final Metric<ASTTypeDeclaration, Integer> NUMBER_OF_PUBLIC_FIELDS =
1✔
401
        Metric.of(JavaMetrics::computeNopa, asClass(always()),
1✔
402
                  "Number of public attributes", "NOPA");
403

404
    /**
405
     * The relative number of method pairs of a class that access in common
406
     * at least one attribute of the measured class. TCC only counts direct
407
     * attribute accesses, that is, only those attributes that are accessed
408
     * in the body of the method.
409
     *
410
     * <p>The value is a double between 0 and 1.
411
     *
412
     * <p>TCC is taken to be a reliable cohesion metric for a class.
413
     * High values (&gt;70%) indicate a class with one basic function,
414
     * which is hard to break into subcomponents. On the other hand, low
415
     * values (&lt;50%) may indicate that the class tries to do too much
416
     * and defines several unrelated services, which is undesirable.
417
     */
418
    public static final Metric<ASTTypeDeclaration, Double> TIGHT_CLASS_COHESION =
1✔
419
        Metric.of(JavaMetrics::computeTcc, asClass(it -> !it.isInterface()),
1✔
420
                  "Tight Class Cohesion", "TCC");
421

422
    /**
423
     * Sum of the statistical complexity of the operations in the class.
424
     * We use CYCLO to quantify the complexity of an operation.
425
     *
426
     * <p>WMC uses the same options as CYCLO, which are provided to CYCLO when computing it ({@link CycloOption}).
427
     */
428
    public static final Metric<ASTTypeDeclaration, Integer> WEIGHED_METHOD_COUNT =
1✔
429
        Metric.of(JavaMetrics::computeWmc, asClass(it -> !it.isInterface()),
1✔
430
                  "Weighed Method Count", "WMC");
431

432

433
    /**
434
     * Number of “functional” public methods divided by the total number
435
     * of public methods. Our definition of “functional method” excludes
436
     * constructors, getters, and setters.
437
     *
438
     * <p>The value is a double between 0 and 1.
439
     *
440
     * <p>This metric tries to quantify whether the measured class’ interface
441
     * reveals more data than behaviour. Low values (less than 30%) indicate
442
     * that the class reveals much more data than behaviour, which is a
443
     * sign of poor encapsulation.
444
     */
445
    public static final Metric<ASTTypeDeclaration, Double> WEIGHT_OF_CLASS =
1✔
446
        Metric.of(JavaMetrics::computeWoc, asClass(it -> !it.isInterface()),
1!
447
                  "Weight Of Class", "WOC");
448

449
    private JavaMetrics() {
450
        // utility class
451
    }
452

453

454
    private static Function<Node, JavaNode> isJavaNode() {
455
        return n -> n instanceof JavaNode ? (JavaNode) n : null;
1!
456
    }
457

458
    private static Function<Node, @Nullable ASTExecutableDeclaration> asMethodOrCtor() {
459

460
        return n -> n instanceof ASTExecutableDeclaration ? (ASTExecutableDeclaration) n : null;
1✔
461
    }
462

463

464
    private static <T extends Node> Function<Node, T> filterMapNode(Class<? extends T> klass, Predicate<? super T> pred) {
465
        return n -> n.asStream().filterIs(klass).filter(pred).first();
1✔
466
    }
467

468

469
    private static Function<Node, ASTTypeDeclaration> asClass(Predicate<? super ASTTypeDeclaration> pred) {
470
        return filterMapNode(ASTTypeDeclaration.class, pred);
1✔
471
    }
472

473

474
    private static int computeNoam(ASTTypeDeclaration node, MetricOptions ignored) {
475
        return node.getDeclarations()
1✔
476
                   .filterIs(ASTMethodDeclaration.class)
1✔
477
                   .filter(JavaRuleUtil::isGetterOrSetter)
1✔
478
                   .count();
1✔
479
    }
480

481
    private static int computeNopa(ASTTypeDeclaration node, MetricOptions ignored) {
482
        return node.getDeclarations()
1✔
483
                   .filterIs(ASTFieldDeclaration.class)
1✔
484
                   .filter(it -> it.hasVisibility(ModifierOwner.Visibility.V_PUBLIC))
1✔
485
                   .flatMap(ASTFieldDeclaration::getVarIds)
1✔
486
                   .count();
1✔
487
    }
488

489
    private static int computeNcss(JavaNode node, MetricOptions options) {
490
        MutableInt result = new MutableInt(0);
1✔
491
        node.acceptVisitor(new NcssVisitor(options, node), result);
1✔
492
        return result.getValue();
1✔
493
    }
494

495
    private static int computeLoc(JavaNode node, MetricOptions ignored) {
496
        // the report location is now not necessarily the entire node.
497
        FileLocation loc = node.getTextDocument().toLocation(node.getTextRegion());
1✔
498

499
        return 1 + loc.getEndLine() - loc.getStartLine();
1✔
500
    }
501

502

503
    private static int computeCyclo(JavaNode node, MetricOptions options) {
504
        MutableInt counter = new MutableInt(0);
1✔
505
        node.acceptVisitor(new CycloVisitor(options, node), counter);
1✔
506
        return counter.getValue();
1✔
507
    }
508

509
    private static BigInteger computeNpath(ASTExecutableDeclaration node, MetricOptions ignored) {
510
        return BigInteger.valueOf(NPathMetricCalculator.computeNpath(node));
×
511
    }
512

513
    private static long computeNpath(ReturnScopeNode node, MetricOptions ignored) {
514
        return NPathMetricCalculator.computeNpath(node);
1✔
515
    }
516

517
    private static int computeCognitive(JavaNode node, MetricOptions ignored) {
518
        State state = new State(node);
1✔
519
        node.acceptVisitor(CognitiveComplexityVisitor.INSTANCE, state);
1✔
520
        return state.getComplexity();
1✔
521
    }
522

523
    private static int computeWmc(ASTTypeDeclaration node, MetricOptions options) {
524
        return (int) MetricsUtil.computeStatistics(CYCLO, node.getOperations(), options).getSum();
1✔
525
    }
526

527

528
    private static double computeTcc(ASTTypeDeclaration node, MetricOptions ignored) {
529
        List<Set<String>> usagesByMethod = attributeAccessesByMethod(node);
1✔
530

531
        int numPairs = numMethodsRelatedByAttributeAccess(usagesByMethod);
1✔
532
        int maxPairs = maxMethodPairs(usagesByMethod.size());
1✔
533

534
        if (maxPairs == 0) {
1✔
535
            return 0;
1✔
536
        }
537

538
        return numPairs / (double) maxPairs;
1✔
539
    }
540

541

542
    /**
543
     * Collects the attribute accesses by method into a map, for TCC.
544
     */
545
    private static List<Set<String>> attributeAccessesByMethod(ASTTypeDeclaration type) {
546
        final List<Set<String>> map = new ArrayList<>();
1✔
547
        final JClassSymbol typeSym = type.getSymbol();
1✔
548
        for (ASTMethodDeclaration decl : type.getDeclarations(ASTMethodDeclaration.class)) {
1✔
549
            Set<String> attrs = new HashSet<>();
1✔
550
            decl.descendants().crossFindBoundaries()
1✔
551
                .filterIs(ASTNamedReferenceExpr.class)
1✔
552
                .forEach(it -> {
1✔
553
                    JVariableSymbol sym = it.getReferencedSym();
1✔
554
                    if (sym instanceof JFieldSymbol && typeSym.equals(((JFieldSymbol) sym).getEnclosingClass())) {
1✔
555
                        attrs.add(sym.getSimpleName());
1✔
556
                    }
557
                });
1✔
558

559
            map.add(attrs);
1✔
560

561
        }
1✔
562
        return map;
1✔
563
    }
564

565

566
    /**
567
     * Gets the number of pairs of methods that use at least one attribute in common.
568
     *
569
     * @param usagesByMethod Map of method name to names of local attributes accessed
570
     *
571
     * @return The number of pairs
572
     */
573
    private static int numMethodsRelatedByAttributeAccess(List<Set<String>> usagesByMethod) {
574
        int methodCount = usagesByMethod.size();
1✔
575
        int pairs = 0;
1✔
576

577
        if (methodCount > 1) {
1✔
578
            for (int i = 0; i < methodCount - 1; i++) {
1✔
579
                for (int j = i + 1; j < methodCount; j++) {
1✔
580
                    if (!Collections.disjoint(usagesByMethod.get(i),
1✔
581
                                              usagesByMethod.get(j))) {
1✔
582
                        pairs++;
1✔
583
                    }
584
                }
585
            }
586
        }
587
        return pairs;
1✔
588
    }
589

590

591
    /**
592
     * Calculates the number of possible method pairs of two methods.
593
     *
594
     * @param methods Number of methods in the class
595
     *
596
     * @return Number of possible method pairs
597
     */
598
    private static int maxMethodPairs(int methods) {
599
        return methods * (methods - 1) / 2;
1✔
600
    }
601

602

603
    /**
604
     * Options for {@link #NCSS}.
605
     */
606
    public enum NcssOption implements MetricOption {
1✔
607
        /** Counts import and package statement. This makes the metric JavaNCSS compliant. */
608
        COUNT_IMPORTS;
1✔
609

610
        /**
611
         * @deprecated Since 7.21.0. When metrics are used for (rule) properties, then the conventional
612
         * enum mapping (from SCREAMING_SNAKE_CASE to camelCase) will be used for the enum values.
613
         * See {@link net.sourceforge.pmd.properties.PropertyFactory#conventionalEnumListProperty(String, Class)}.
614
         */
615
        @Deprecated
616
        @Override
617
        public String valueName() {
NEW
618
            return MetricOption.super.valueName();
×
619
        }
620
    }
621

622

623
    /**
624
     * Options for {@link #CYCLO}.
625
     */
626
    public enum CycloOption implements MetricOption {
1✔
627
        /** Do not count the paths in boolean expressions as decision points. */
628
        IGNORE_BOOLEAN_PATHS,
1✔
629
        /** Consider assert statements as if they were {@code if (..) throw new AssertionError(..)}. */
630
        CONSIDER_ASSERT;
1✔
631

632
        /**
633
         * @deprecated Since 7.21.0. When metrics are used for (rule) properties, then the conventional
634
         * enum mapping (from SCREAMING_SNAKE_CASE to camelCase) will be used for the enum values.
635
         * See {@link net.sourceforge.pmd.properties.PropertyFactory#conventionalEnumListProperty(String, Class)}.
636
         */
637
        @Deprecated
638
        @Override
639
        public String valueName() {
NEW
640
            return MetricOption.super.valueName();
×
641
        }
642
    }
643

644
    private static int computeAtfd(JavaNode node, MetricOptions ignored) {
645
        MutableInt result = new MutableInt(0);
1✔
646
        node.acceptVisitor(new AtfdBaseVisitor(), result);
1✔
647
        return result.getValue();
1✔
648
    }
649

650

651
    private static double computeWoc(ASTTypeDeclaration node, MetricOptions ignored) {
652
        NodeStream<ASTMethodDeclaration> methods =
1✔
653
            node.getDeclarations()
1✔
654
                .filterIs(ASTMethodDeclaration.class)
1✔
655
                .filter(it -> !it.hasVisibility(ModifierOwner.Visibility.V_PRIVATE));
1✔
656

657
        int notSetter = methods.filter(it -> !JavaRuleUtil.isGetterOrSetter(it)).count();
1✔
658
        int total = methods.count();
1✔
659
        if (total == 0) {
1✔
660
            return 0;
1✔
661
        }
662
        return notSetter / (double) total;
1✔
663
    }
664

665

666
    private static int computeFanOut(JavaNode node, MetricOptions options) {
667
        Set<JClassSymbol> cfo = new HashSet<>();
1✔
668
        node.acceptVisitor(ClassFanOutVisitor.getInstance(options), cfo);
1✔
669
        return cfo.size();
1✔
670
    }
671

672

673
    /**
674
     * Options for {@link #FAN_OUT}.
675
     */
676
    public enum ClassFanOutOption implements MetricOption {
1✔
677
        /** Whether to include classes in the {@code java.lang} package. */
678
        INCLUDE_JAVA_LANG;
1✔
679

680
        /**
681
         * @deprecated Since 7.21.0. When metrics are used for (rule) properties, then the conventional
682
         * enum mapping (from SCREAMING_SNAKE_CASE to camelCase) will be used for the enum values.
683
         * See {@link net.sourceforge.pmd.properties.PropertyFactory#conventionalEnumListProperty(String, Class)}.
684
         */
685
        @Deprecated
686
        @Override
687
        public String valueName() {
NEW
688
            return MetricOption.super.valueName();
×
689
        }
690
    }
691
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc