diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index 16874206b..361b1d838 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -3,7 +3,7 @@ entries: - title: sidebar - product: PMD Project + product: PMD version: "!PMD_VERSION!" folders: @@ -105,6 +105,9 @@ entries: - title: JSP Support url: /pmd_languages_jsp.html output: web, pdf + - title: Java code metrics + url: /pmd_java_metrics_index.html + output: web, pdf - title: Developer Documentation output: web, pdf folderitems: @@ -144,9 +147,6 @@ entries: - title: Using code metrics in custom rules url: /pmd_devdocs_metrics_howto.html output: web, pdf - - title: Index of code metrics - url: /pmd_devdocs_metrics_index.html - output: web, pdf - title: Making Rulesets url: /pmd_devdocs_making_rulesets.html output: web, pdf diff --git a/docs/pages/pmd/devdocs/metrics_howto.md b/docs/pages/pmd/devdocs/metrics_howto.md index 9eb3294a0..71eedd01a 100644 --- a/docs/pages/pmd/devdocs/metrics_howto.md +++ b/docs/pages/pmd/devdocs/metrics_howto.md @@ -1,9 +1,9 @@ --- title: Using code metrics in custom rules tags: [customizing] -summary: "PMD was recently enhanced with the ability to compute code metrics on Java code (the so-called Metrics -Framework). This framework provides developers with a straightforward interface to use code metrics in their rules, and -to extend the framework with their own custom metrics." +summary: "PMD was recently enhanced with the ability to compute code metrics on Java and Apex source (the so-called +Metrics Framework). This framework provides developers with a straightforward interface to use code metrics in their +rules, and to extend the framework with their own custom metrics." last_updated: July 20, 2017 sidebar: pmd_sidebar permalink: pmd_devdocs_metrics_howto.html @@ -13,4 +13,73 @@ folder: pmd/devdocs ## Using the metrics framework -## Writing custom metrics \ No newline at end of file +{%include note.html content="Using the metrics framework is for now restricted to Java rules (with plans to support +XPath rules later)." %} + +To use the metrics framework in a custom rule, the first thing to do would be to enable metrics by adding the +`metrics="true"` attribute to your rule's XML element. + +{%include note.html content="The following explains how to use the Java metrics framework. The Apex framework +differs only by the name of its classes." %} + +In PMD's Metrics framework, a metric is an operation that can be carried out on nodes of a certain type and produces +a numeric result. In the Java framework, metrics can be computed on operation declaration nodes (constructor and +method declaration), and type declaration nodes (class, interface, enum, and annotation declarations). A metric +object in the framework can only handle either types or operations, but not both. + +The framework provides a library of already implemented metrics. These metrics are referenced by `MetricKey` objects, +which are listed in two public enums: `JavaClassMetricKey` and `JavaOperationMetricKey`. Metric keys wrap a metric, and +know which type of node their metric can be computed on. That way, you cannot compute an operation metric on a class +declaration node. Metrics that can be computed on operation and type declarations (e.g. NCSS) have one metric key in +each enum. + +The static façade class `JavaMetrics` is the only entry point to compute metrics in the Java framework. +This class provides the method `get` and its overloads. The following sections describes the interface of this class. + +### Basic usage + +The simplest overloads of `JavaMetrics.get` take two parameters: **a `MetricKey` and a node of the corresponding type.** +Say you want to write a rule to report methods that have a high cyclomatic complexity. In your rule's visitor, you +can get the value of Cyclo for a method node like so: +```java +public Object visit(ASTMethodDeclaration node, Object data) { + int cyclo = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node); + if (cyclo > 10) { + // add violation + } + return data; +} +``` + +The same goes for class metrics: you select one among `JavaClassMetricKey`'s constants and pass it along with the node + to `JavaMetrics.get`. + +### Capability checking + +Metrics are not necessarily computable on any node of the type they handle. For example, Cyclo cannot be computed on +abstract methods. Metric keys provides a `supports(Node)` boolean method to find out if the metric can be computed on +the specified node. **If the metric cannot be computed on the given node, `JavaMetrics.get` will return `Double.NaN` .** +If you're concerned about that, you can condition your call on whether the node is supported or not: +```java +public Object visit(ASTMethodDeclaration node, Object data) { + if (JavaOperationMetricKey.CYCLO.supports(node)) { + int cyclo = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node); + if (cyclo > 10) { + // add violation + } + return data; + } +} +``` + +### Metric versions + +{%include important.html content="Metric versions are about to be revamped into options that can be combined +together." %} + + +### Result options + +## Writing custom metrics + +{%include warning.html content="WIP" %} diff --git a/docs/pages/pmd/devdocs/metrics_index.md b/docs/pages/pmd/devdocs/metrics_index.md deleted file mode 100644 index 4f558f2a6..000000000 --- a/docs/pages/pmd/devdocs/metrics_index.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Index of code metrics -tags: [customizing] -summary: "Index of the code metrics available out of the box to Java rule developers." -last_updated: July 20, 2017 -sidebar: pmd_sidebar -permalink: pmd_devdocs_metrics_index.html -folder: pmd/devdocs -toc: - minimumHeaders: 8 ---- -# Index of code metrics - -## Access to Foreign Data (ATFD) - -*Operation metric, class metric.* - -### Description - - -## Cyclomatic Complexity (CYCLO) - -*Operation metric.* Can be calculated on any non-abstract method. - -### Description - -Number of independent paths through a block of code \[[Lanza05](#Lanza05)\]. -Formally, given that the control flow graph of the block has n vertices, e edges and p connected components, the -Cyclomatic complexity of the block is given by `CYCLO = e - n + 2p` \[[McCabe76](#McCabe76)\]. In practice it can be -calculated by counting control flow statements following the standard rules given below. - -The standard version of the metric complies with McCabe's original definition: - -* Methods have a base complexity of 1. -* +1 for every control flow statement (`if`, `case`, `catch`, `throw`, `do`, `while`, `for`, `break`, `continue`) and -conditional expression (`?:`) \[[Sonarqube](#Sonarqube)\]. Notice switch cases count as one, but not the switch itself: the point is that - a switch should have the same complexity value as the equivalent series of `if` statements. -* `else`, `finally` and `default` don't count; -* +1 for every boolean operator (`&&`, `||`) in the guard condition of a control flow statement. That's because -Java has short-circuit evaluation semantics for boolean operators, which makes every boolean operator kind of a -control flow statement in itself. - -### Versions - -* Version `CycloVersion#IGNORE_BOOLEAN_PATHS`: Boolean operators are not counted, nor are empty - fall-through cases in `switch` statements. You can use this version to get results - similar to those of the old `StdCyclomaticComplexityRule`, which is to be replaced. - - -## Lines of Code (LoC) - -*Operation metric, class metric.* Can be calculated on any of those nodes. - -### Description - -Simply counts the number of lines of code the operation or class takes up in the source. This metric doesn't discount - comments or blank lines. - - -## Non-commenting source statements (NCSS) - -*Operation metric, class metric.* Can be calculated on any of those nodes. - -### Description - -Number of statements in a class or operation. That's roughly equivalent to counting the number of semicolons and -opening braces in the program. Comments and blank lines are ignored, and statements spread on multiple lines count as - only one (e.g. `int\n a;` counts a single statement). - -The standard version of the metric is based off JavaNCSS's version \[[JavaNcss](#JavaNcss)\]: - -* +1 for any of the following statements: `if`, `else`, `while`, `do`, `for`, `switch`, `break`, `continue`, `return`, -`throw`, `synchronized`, `catch`, `finally`. -* +1 for each assignment, variable declaration (except `for` loop initializers) or statement expression. We count -variables declared on the same line (e.g. `int a, b, c;`) as a single statement. -* Contrary to Sonarqube, but as JavaNCSS, we count type declarations (class, interface, enum, annotation), -and method and field declarations \[[Sonarqube](#Sonarqube)\]. -* Contrary to JavaNCSS, but as Sonarqube, we do not count package declaration and import declarations as statements. -This makes it easier to compare nested classes to outer classes. Besides, it makes for class metric results that -actually represent the size of the class and not of the file. If you don't like that behaviour, use the `JAVANCSS` -version. - - -### Versions - -* Version `NcssVersion#JAVANCSS`: Import and package statements are counted as well. This version fully complies with - JavaNCSS. - - -## Weighted Method Count (WMC) - -*Class metric.* Can be computed on classes and enums - -### Description - -Sum of the statistical complexity of the operations in the class. We use [CYCLO](#cyclomatic-complexity-cyclo) to -quantify the complexity of an operation \[[Lanza05](#Lanza05)\]. - -### Versions - -WMC uses the same versions as CYCLO, which have the effect of summing that version of CYCLO to calculate the metric. - -# References - -Lanza05: Lanza, Marinescu; Object-Oriented Metrics in Practice, 2005. - -McCabe76: McCabe, A Complexity Measure, in Proceedings of the 2nd ICSE (1976). - -Sonarqube: [Sonarqube online documentation.](https://docs.sonarqube.org/display/SONAR/Metric+Definitions) - -JavaNcss: [JavaNCSS online documentation.](http://www.kclee.de/clemens/java/javancss/) \ No newline at end of file diff --git a/docs/pages/pmd/languages/java_metrics_index.md b/docs/pages/pmd/languages/java_metrics_index.md new file mode 100644 index 000000000..f73b31b4e --- /dev/null +++ b/docs/pages/pmd/languages/java_metrics_index.md @@ -0,0 +1,228 @@ +--- +title: Index of code metrics +tags: [customizing] +summary: "Index of the code metrics available out of the box to Java rule developers." +last_updated: July 20, 2017 +sidebar: pmd_sidebar +permalink: pmd_java_metrics_index.html +folder: pmd/languages +toc: + minimumHeaders: 8 +--- +# Index of code metrics + +## Access to Foreign Data (ATFD) + +*Operation metric, class metric.* + +### Description + + +## Cyclomatic Complexity (CYCLO) + +*Operation metric.* Can be calculated on any non-abstract operation. + +### Description + +Number of independent paths through a block of code \[[Lanza05](#Lanza05)\]. +Formally, given that the control flow graph of the block has n vertices, e edges and p connected components, the +cyclomatic complexity of the block is given by `CYCLO = e - n + 2p` \[[McCabe76](#McCabe76)\]. In practice it can be +calculated by counting control flow statements following the standard rules given below. + +The standard version of the metric complies with McCabe's original definition: + +* Methods have a base complexity of 1. +* +1 for every control flow statement (`if`, `case`, `catch`, `throw`, `do`, `while`, `for`, `break`, `continue`) and + conditional expression (`?:`) \[[Sonarqube](#Sonarqube)\]. Notice switch cases count as one, but not the switch + itself: the point is that a switch should have the same complexity value as the equivalent series of `if` statements. +* `else`, `finally` and `default` don't count; +* +1 for every boolean operator (`&&`, `||`) in the guard condition of a control flow statement. That's because + Java has short-circuit evaluation semantics for boolean operators, which makes every boolean operator kind of a + control flow statement in itself. + +### Code examples + +```java +class Foo { + void baseCyclo() { // Cyclo = 1 + highCyclo(); + } + + void highCyclo() { // Cyclo = 10 + int x = 0, y = 2; + boolean a = false, b = true; + + if (a && (y == 1 ? b : true)) { // +3 + if (y == x) { // +1 + while (true) { // +1 + if (x++ < 20) { // +1 + break; // +1 + } + } + } else if (y == t && !d) { // +2 + x = a ? y : x; // +1 + } else { + x = 2; + } + } + } +} +``` +### Versions + +* Version `CycloVersion#IGNORE_BOOLEAN_PATHS`: Boolean operators are not counted, nor are empty + fall-through cases in `switch` statements. You can use this version to get results + similar to those of the old `StdCyclomaticComplexityRule`, which is to be replaced. + + +## Lines of Code (LoC) + +*Operation metric, class metric.* Can be calculated on any of those nodes. + +### Description + +Simply counts the number of lines of code the operation or class takes up in the source. This metric doesn't discount +comments or blank lines. See also [NCSS](#non-commenting-source-statements-ncss). + + +## Non-commenting source statements (NCSS) + +*Operation metric, class metric.* Can be calculated on any of those nodes. + +### Description + +Number of statements in a class or operation. That's roughly equivalent to counting the number of semicolons and +opening braces in the program. Comments and blank lines are ignored, and statements spread on multiple lines count as + only one (e.g. `int\n a;` counts a single statement). + +The standard version of the metric is based off JavaNCSS's version \[[JavaNcss](#JavaNcss)\]: + +* +1 for any of the following statements: `if`, `else`, `while`, `do`, `for`, `switch`, `break`, `continue`, `return`, + `throw`, `synchronized`, `catch`, `finally`. +* +1 for each assignment, variable declaration (except `for` loop initializers) or statement expression. We count + variables declared on the same line (e.g. `int a, b, c;`) as a single statement. +* Contrary to Sonarqube, but as JavaNCSS, we count type declarations (class, interface, enum, annotation), + and method and field declarations \[[Sonarqube](#Sonarqube)\]. +* Contrary to JavaNCSS, but as Sonarqube, we do not count package declaration and import declarations as statements. + This makes it easier to compare nested classes to outer classes. Besides, it makes for class metric results that + actually represent the size of the class and not of the file. If you don't like that behaviour, use the `JAVANCSS` + version. + +### Code example +```java +import java.util.Collections; // +0 +import java.io.IOException; // +0 + +class Foo { // +1, total Ncss = 12 + + public void bigMethod() // +1 + throws IOException { + int x = 0, y = 2; // +1 + boolean a = false, b = true; // +1 + + if (a || b) { // +1 + try { // +1 + do { // +1 + x += 2; // +1 + } while (x < 12); + + System.exit(0); // +1 + } catch (IOException ioe) { // +1 + throw new PatheticFailException(ioe); // +1 + } + } else { + assert false; // +1 + } + } +} +``` + + +### Versions + +* Version `NcssVersion#JAVANCSS`: Import and package statements are counted as well. This version fully complies with + JavaNCSS. + +## NPath complexity (NPath) + +*Operation metric.* Can be computed on any non-abstract operation. + +### Description + +Number of acyclic execution paths through a piece of code. This is related to cyclomatic complexity, but the two +metrics don't count the same thing: NPath counts the number of distinct *full* paths from the beginning to the end of +the method, while Cyclo only counts the number of decision points. NPath is not computed as simply as Cyclo. With +NPath, two decision points appearing sequentially have their complexity multiplied. + +The fact that NPath multiplies the complexity of statements makes it grow exponentially: 10 `if` - `else` statements in +a row would give an NPath of 1024, while Cyclo would evaluate to 20. Methods with an NPath complexity over 200 are +generally considered too complex. + +We compute NPath recursively, with the following set of rules: +* An empty block has a complexity of 1. +* The complexity of a block is the product of the NPath complexity of its statements, calculated as follows: + * The complexity of `for`, `do` and `while` statements is 1, plus the complexity of the block, plus the complexity of + the guard condition. + * The complexity of a cascading `if` statement (`if .. else if ..`) is the number of `if` statements in the chain, + plus the complexity of their guard condition, plus the complexity of the unguarded `else` block (or 1 if there is + none). + * The complexity of a `switch` statement is the number of cases, plus the complexity of each `case` block. It's + equivalent to the complexity of the equivalent cascade of `if` statements. + * The complexity of a ternary expression (`?:`) is the complexity of the guard condition, plus the + complexity of both expressions. It's equivalent to the complexity of the equivalent `if .. else` construct. + * The complexity of a `try .. catch` statement is the complexity of the `try` block, plus the complexity of each + catch block. + * The complexity of a `return` statement is the complexity of the expression (or 1 if there is none). + * All other statements have a complexity of 1 and are discarded from the product. + +### Code example + +```java +void fun(boolean a, boolean b, boolean c) { // NPath = 6 + + // block #0 + + if (a) { + // block #1 + } else { + // block #2 + } + + // block #3 + + if (b) { + // block #4 + } else if (c) { + // block #5 + } + + // block #6 +} +``` +After block 0, the control flow can either execute block 1 or 2 before jumping to block 3. From block three, the +control flow will again have the choice between blocks 4 and 5 before jumping to block 6. The first `if` offers 2 +choices, the second offers 3, so the cyclomatic complexity of this method is 2 + 3 = 5. NPath, however, sees 2 * 3 = +6 full paths from the beginning to the end. + +## Weighted Method Count (WMC) + +*Class metric.* Can be computed on classes and enums + +### Description + +Sum of the statistical complexity of the operations in the class. We use [CYCLO](#cyclomatic-complexity-cyclo) to +quantify the complexity of an operation \[[Lanza05](#Lanza05)\]. + +### Versions + +WMC uses the same versions as CYCLO, which have the effect of summing that version of CYCLO to calculate the metric. + +# References + +Lanza05: Lanza, Marinescu; Object-Oriented Metrics in Practice, 2005. + +McCabe76: McCabe, A Complexity Measure, in Proceedings of the 2nd ICSE (1976). + +Sonarqube: [Sonarqube online documentation.](https://docs.sonarqube.org/display/SONAR/Metric+Definitions) + +JavaNcss: [JavaNCSS online documentation.](http://www.kclee.de/clemens/java/javancss/) \ No newline at end of file diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/DefaultNcssVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/DefaultNcssVisitor.java index 082e55cea..79da9a5e2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/DefaultNcssVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/DefaultNcssVisitor.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.mutable.MutableInt; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAssertStatement; import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; @@ -220,4 +221,10 @@ public Object visit(ASTInitializer node, Object data) { return super.visit(node, data); } + @Override + public Object visit(ASTAssertStatement node, Object data) { + ((MutableInt) data).increment(); + return super.visit(node, data); + } + } diff --git a/pmd-java/src/main/resources/rulesets/java/metrics.xml b/pmd-java/src/main/resources/rulesets/java/metrics.xml index 422a7e56f..5a35fb5bb 100644 --- a/pmd-java/src/main/resources/rulesets/java/metrics.xml +++ b/pmd-java/src/main/resources/rulesets/java/metrics.xml @@ -15,52 +15,47 @@ class="net.sourceforge.pmd.lang.java.metrics.rule.CyclomaticComplexityRule" metrics="true" externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#CyclomaticComplexity"> - - + 3 @@ -71,24 +66,38 @@ public class Foo { // This has a Cyclomatic Complexity = 12 since="3.9" class="net.sourceforge.pmd.lang.java.metrics.rule.NcssCountRule" metrics="true" - externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#NcssTypeCount"> + externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#NcssCount"> - This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines - of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, - lines of code that are split are counted as one. + This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of lines + of code in a class, method or constructor. NCSS ignores comments, blank lines, and only counts actual + statements. For more details on the calculation, see the documentation ofthe[NCSSmetric](/pmd_java_metrics_index.html#non-commenting-source-statements-ncss). 3 @@ -103,41 +112,51 @@ public class Foo extends Bar { externalInfoUrl="${pmd.website.baseurl}/rules/java/codesize.html#NPathComplexity"> The NPath complexity of a method is the number of acyclic execution paths through that method. + While cyclomatic complexity counts the number of decision points in a method, NPath counts the number of + full paths from the beginning to the end of the block of the method. That metric grows exponentially, as + it multiplies the complexity of statements in the same block. For more details on the calculation, see the + documentation of the [NPath metric](/pmd_java_metrics_index.html#npath-complexity-npath). + A threshold of 200 is generally considered the point where measures should be taken to reduce complexity and increase readability. 3 r) { - doSomething(); - while (f < 5 ) { - anotherThing(); - f -= 27; - } - } else { - tryThis(); - } +public class Foo { + public static void bar() { // Ncss = 252: reported! + boolean a, b = true; + try { // 2 * 2 + 2 = 6 + if (true) { // 2 + List buz = new ArrayList(); + } + + for(int i = 0; i < 19; i++) { // * 2 + List buz = new ArrayList(); + } + } catch(Exception e) { + if (true) { // 2 + e.printStackTrace(); } } - if ( r - n > 45) { - while (doMagic()) { - findRabbits(); - } + + while (j++ < 20) { // * 2 + List buz = new ArrayList(); } - try { - doSomethingDangerous(); - } catch (Exception ex) { - makeAmends(); - } finally { - dontDoItAgain(); + + switch(j) { // * 7 + case 1: + case 2: break; + case 3: j = 5; break; + case 4: if (b && a) { bar(); } break; + default: break; } + + do { // * 3 + List buz = new ArrayList(); + } while (a && j++ < 30); } } - ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml index ecf84267f..80c26d932 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml @@ -104,6 +104,7 @@ e.printStackTrace(); } catch (ThemAll pokemon) { pokemon.train(); + assert pokemon.level > 12; } finally { // Do nothing } @@ -125,11 +126,11 @@ true 5 - 'com.company.money.Foo' has value 65 highest 20. + 'com.company.money.Foo' has value 66 highest 21. 'com.company.money.Foo#Foo()' has value 2. 'com.company.money.Foo#Foo(int)' has value 13. 'com.company.money.Foo#foo()' has value 14. - 'com.company.money.Foo#main(String)' has value 20. + 'com.company.money.Foo#main(String)' has value 21. @@ -140,11 +141,11 @@ javaNcss 5 - 'com.company.money.Foo' has value 68 highest 20. + 'com.company.money.Foo' has value 69 highest 21. 'com.company.money.Foo#Foo()' has value 2. 'com.company.money.Foo#Foo(int)' has value 13. 'com.company.money.Foo#foo()' has value 14. - 'com.company.money.Foo#main(String)' has value 20. + 'com.company.money.Foo#main(String)' has value 21. diff --git a/src/site/markdown/overview/changelog.md b/src/site/markdown/overview/changelog.md index 4503e587a..5e45ae798 100644 --- a/src/site/markdown/overview/changelog.md +++ b/src/site/markdown/overview/changelog.md @@ -157,8 +157,9 @@ Al existing rules have been updated to reflect these changes. If you have custom * [#524](https://github.com/pmd/pmd/pull/524): \[java] Add support for explicit type arguments with method invocation - [Bendegúz Nagy](https://github.com/WinterGrascph) * [#525](https://github.com/pmd/pmd/pull/525): \[core] Fix line ending and not ignored files issues - [Matias Comercio](https://github.com/MatiasComercio) * [#528](https://github.com/pmd/pmd/pull/528): \[core] Fix typo - [Ayoub Kaanich](https://github.com/kayoub5) +* [#529](https://github.com/pmd/pmd/pull/529): \[java] Abstracted the Java metrics framework - [Clément Fournier](https://github.com/oowekyala) * [#530](https://github.com/pmd/pmd/pull/530): \[java] Fix issue #527: Lombok getter annotation on enum is not recognized correctly - [Clément Fournier](https://github.com/oowekyala) * [#535](https://github.com/pmd/pmd/pull/535): \[apex] Fix broken Apex visitor adapter - [Clément Fournier](https://github.com/oowekyala) -* [#529](https://github.com/pmd/pmd/pull/529): \[java] Abstracted the Java metrics framework - [Clément Fournier](https://github.com/oowekyala) * [#542](https://github.com/pmd/pmd/pull/542): \[java] Metrics abstraction - [Clément Fournier](https://github.com/oowekyala) +* [#548](https://github.com/pmd/pmd/pull/548): \[java] Metrics documentation - [Clément Fournier](https://github.com/oowekyala)