diff --git a/docs/pages/pmd/languages/java_metrics_index.md b/docs/pages/pmd/languages/java_metrics_index.md
index c093fa46bb..84fdf4a809 100644
--- a/docs/pages/pmd/languages/java_metrics_index.md
+++ b/docs/pages/pmd/languages/java_metrics_index.md
@@ -226,9 +226,15 @@ 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.
+## Number Of Public Attributes (NOPA)
+*Class metric.* Can be computed on classes.
+
+## Number Of Accessor Methods (NOAM)
+*Class metric.* Can be computed on classes.
+
## Weighted Method Count (WMC)
-*Class metric.* Can be computed on classes and enums
+*Class metric.* Can be computed on classes and enums.
### Description
@@ -241,6 +247,25 @@ Sum of the statistical complexity of the operations in the class. We use
WMC uses the same options as CYCLO, which are provided to CYCLO when
computing it.
+## Weight Of Class (WOC)
+
+*Class metric.* Can be computed on classes.
+
+### Description
+
+Number of "functional" public methods divided by the total number of
+public methods. Our definition of "functional method" excludes
+constructors, getters, and setters.
+
+This metric tries to quantify whether the measured class' interface reveals
+more data than behaviour. Low values (less than 30%) indicate that the class
+reveals much more data than behaviour, which is a sign of poor encapsulation.
+
+This metric is used to detect Data Classes, in conjunction with [WMC](#weighted-method-count-wmc),
+[NOPA](#number-of-public-attributes-nopa) and [NOAM](#number-of-accessor-methods-noam).
+
+
+
# References
Lanza05: Lanza, Marinescu; Object-Oriented Metrics in Practice, 2005.
@@ -249,4 +274,4 @@ computing it.
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
+JavaNcss: [JavaNCSS online documentation.](http://www.kclee.de/clemens/java/javancss/)
diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index 2226557e7a..200439eb31 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -232,5 +232,5 @@ All existing rules have been updated to reflect these changes. If you have custo
* [#557](https://github.com/pmd/pmd/pull/557): \[java] Fix NPath metric not counting ternaries correctly - [Clément Fournier](https://github.com/oowekyala)
* [#563](https://github.com/pmd/pmd/pull/563): \[java] Add support for basic method type inference for strict invocation - [Bendegúz Nagy](https://github.com/WinterGrascph)
* [#567](https://github.com/pmd/pmd/pull/567): \[java] Last API change for metrics (metric options) - [Clément Fournier](https://github.com/oowekyala)
+* [#573](https://github.com/pmd/pmd/pull/573): \[java] Data class rule - [Clément Fournier](https://github.com/oowekyala)
* [#576](https://github.com/pmd/pmd/pull/576): \[doc][java] Add hint for Guava users in InefficientEmptyStringCheck - [mmoehring](https://github.com/mmoehring)
-
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
index 9447de8024..1461eba2d2 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java
@@ -224,7 +224,7 @@ public interface Node {
* @param xpathString
* the expression to check
* @return List of all matching nodes. Returns an empty list if none found.
- * @throws JaxenException
+ * @throws JaxenException if the xpath is incorrect or fails altogether
*/
List extends Node> findChildNodesWithXPath(String xpathString) throws JaxenException;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java
index 19670c6b90..843fb567e8 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java
@@ -55,7 +55,7 @@ public final class JavaQualifiedName implements QualifiedName {
* @return The qualified name of the node
*/
/* default */ static JavaQualifiedName ofOperation(ASTConstructorDeclaration node) {
- ASTClassOrInterfaceDeclaration parent = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class);
+ ASTAnyTypeDeclaration parent = node.getFirstParentOfType(ASTAnyTypeDeclaration.class);
return ofOperation(parent.getQualifiedName(),
parent.getImage(),
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java
index 44ee6f5c5d..e59007d938 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java
@@ -23,6 +23,7 @@ import net.sourceforge.pmd.lang.metrics.Metric;
*/
public abstract class AbstractJavaMetric implements Metric {
+
protected List findAllCalls(ASTMethodOrConstructorDeclaration node) {
List result = new ArrayList<>();
// TODO:cf findAllCalls
@@ -32,6 +33,18 @@ public abstract class AbstractJavaMetric implements Metric {
}
+ @Override
+ public final boolean equals(Object o) {
+ return o != null && o.getClass() == this.getClass();
+ }
+
+
+ @Override
+ public final int hashCode() {
+ return getClass().hashCode();
+ }
+
+
/**
* Gives access to a signature matcher to metrics. They can use it to perform signature matching.
*
@@ -41,14 +54,4 @@ public abstract class AbstractJavaMetric implements Metric {
return JavaMetrics.getFacade().getTopLevelPackageStats();
}
- @Override
- public final boolean equals(Object o) {
- return o != null && o.getClass() == this.getClass();
- }
-
- @Override
- public final int hashCode() {
- return getClass().hashCode();
- }
-
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java
index 544af36631..9ef5fd0ed2 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaClassMetricKey.java
@@ -8,7 +8,10 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.metrics.impl.AtfdMetric.AtfdClassMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.LocMetric.LocClassMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.NcssMetric.NcssClassMetric;
+import net.sourceforge.pmd.lang.java.metrics.impl.NoamMetric;
+import net.sourceforge.pmd.lang.java.metrics.impl.NopaMetric;
import net.sourceforge.pmd.lang.java.metrics.impl.WmcMetric;
+import net.sourceforge.pmd.lang.java.metrics.impl.WocMetric;
import net.sourceforge.pmd.lang.metrics.MetricKey;
/**
@@ -42,7 +45,29 @@ public enum JavaClassMetricKey implements MetricKey {
*
* @see net.sourceforge.pmd.lang.java.metrics.impl.LocMetric
*/
- LOC(new LocClassMetric());
+ LOC(new LocClassMetric()),
+
+ /**
+ * Number of Public Attributes.
+ *
+ * @see NopaMetric
+ */
+ NOPA(new NopaMetric()),
+
+ /**
+ * Number of Accessor Methods.
+ *
+ * @see NopaMetric
+ */
+ NOAM(new NoamMetric()),
+
+ /**
+ * Weight of class.
+ *
+ * @see WocMetric
+ */
+ WOC(new WocMetric());
+
private final JavaClassMetric calculator;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java
index 142a2e06b1..045b7a8759 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java
@@ -4,10 +4,19 @@
package net.sourceforge.pmd.lang.java.metrics.impl;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
+import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
import net.sourceforge.pmd.lang.java.metrics.AbstractJavaMetric;
import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetric;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
/**
* Base class for class metrics.
@@ -28,4 +37,89 @@ public abstract class AbstractJavaClassMetric extends AbstractJavaMetric decls = getMethodsAndConstructors(classNode);
+
+ for (ASTMethodOrConstructorDeclaration decl : decls) {
+ if (mask.covers(decl.getSignature())) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+
+ /**
+ * Counts the fields matching the signature mask in this class.
+ *
+ * @param classNode The class on which to count
+ * @param mask The mask
+ *
+ * @return The number of fields matching the signature mask
+ */
+ protected int countMatchingFieldSigs(ASTAnyTypeDeclaration classNode, JavaFieldSigMask mask) {
+ int count = 0;
+ List decls = getFields(classNode);
+
+ for (ASTFieldDeclaration decl : decls) {
+ if (mask.covers(decl.getSignature())) {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+
+ /**
+ * Gets a list of all methods and constructors declared in the class.
+ *
+ * @param node The class
+ *
+ * @return The list of all methods and constructors
+ */
+ protected List getMethodsAndConstructors(ASTAnyTypeDeclaration node) {
+ return getDeclarationsOfType(node, ASTMethodOrConstructorDeclaration.class);
+ }
+
+
+ /**
+ * Gets a list of all fields declared in the class.
+ *
+ * @param node The class
+ *
+ * @return The list of all fields
+ */
+ protected List getFields(ASTAnyTypeDeclaration node) {
+ return getDeclarationsOfType(node, ASTFieldDeclaration.class);
+ }
+
+
+ private List getDeclarationsOfType(ASTAnyTypeDeclaration node, Class tClass) {
+
+ List result = new ArrayList<>();
+ List decls = node.getDeclarations();
+
+ for (ASTAnyTypeBodyDeclaration decl : decls) {
+ if (tClass.isInstance(decl.jjtGetChild(0))) {
+ result.add(tClass.cast(decl.jjtGetChild(0)));
+ }
+ }
+
+ return result;
+ }
+
+
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java
new file mode 100644
index 0000000000..c94237d35c
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java
@@ -0,0 +1,37 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.impl;
+
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
+import net.sourceforge.pmd.lang.metrics.MetricOptions;
+
+/**
+ * Number of accessor methods.
+ *
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class NoamMetric extends AbstractJavaClassMetric {
+
+ @Override
+ public boolean supports(ASTAnyTypeDeclaration node) {
+ return node.getTypeKind() == TypeKind.CLASS;
+ }
+
+
+ @Override
+ public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
+ JavaOperationSigMask mask = new JavaOperationSigMask();
+ mask.restrictRolesTo(Role.GETTER_OR_SETTER);
+ mask.restrictVisibilitiesTo(Visibility.PUBLIC);
+
+
+ return (double) countMatchingOpSigs(node, mask);
+ }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java
new file mode 100644
index 0000000000..e19fa2761b
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java
@@ -0,0 +1,36 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.impl;
+
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
+import net.sourceforge.pmd.lang.metrics.MetricOptions;
+
+/**
+ * Number of public attributes.
+ *
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class NopaMetric extends AbstractJavaClassMetric {
+
+ @Override
+ public boolean supports(ASTAnyTypeDeclaration node) {
+ return node.getTypeKind() == TypeKind.CLASS;
+ }
+
+
+ @Override
+ public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
+
+ JavaFieldSigMask mask = new JavaFieldSigMask();
+ mask.restrictVisibilitiesTo(Visibility.PUBLIC);
+
+ return (double) countMatchingFieldSigs(node, mask);
+ }
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java
new file mode 100644
index 0000000000..b3be183fdf
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java
@@ -0,0 +1,43 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.impl;
+
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role;
+import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility;
+import net.sourceforge.pmd.lang.metrics.MetricOptions;
+
+/**
+ * Weight of class.
+ *
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class WocMetric extends AbstractJavaClassMetric {
+
+ @Override
+ public boolean supports(ASTAnyTypeDeclaration node) {
+ return node.getTypeKind() == TypeKind.CLASS;
+ }
+
+
+ @Override
+ public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) {
+
+ JavaOperationSigMask mask = new JavaOperationSigMask();
+ mask.forbid(Role.CONSTRUCTOR, Role.GETTER_OR_SETTER);
+ mask.restrictVisibilitiesTo(Visibility.PUBLIC);
+
+ int functionalMethods = countMatchingOpSigs(node, mask);
+
+ mask.coverAllRoles();
+
+ int totalPublicMethods = countMatchingOpSigs(node, mask);
+
+ return functionalMethods / (double) totalPublicMethods;
+ }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java
new file mode 100644
index 0000000000..777b9df2d6
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java
@@ -0,0 +1,54 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.rule;
+
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
+import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule;
+
+/**
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class DataClassRule extends AbstractJavaMetricsRule {
+
+ // probably not worth using properties
+ private static final int ACCESSOR_OR_FIELD_FEW_LEVEL = 3;
+ private static final int ACCESSOR_OR_FIELD_MANY_LEVEL = 5;
+ private static final double WOC_LEVEL = 1. / 3.;
+ private static final int WMC_HIGH_LEVEL = 31;
+ private static final int WMC_VERY_HIGH_LEVEL = 47;
+
+
+ @Override
+ public Object visit(ASTAnyTypeDeclaration node, Object data) {
+
+ boolean isDataClass = interfaceRevealsData(node) && classRevealsDataAndLacksComplexity(node);
+
+ if (isDataClass) {
+ addViolation(data, node, new String[] {node.getImage()});
+ }
+
+ return super.visit(node, data);
+ }
+
+
+ private boolean interfaceRevealsData(ASTAnyTypeDeclaration node) {
+ double woc = JavaMetrics.get(JavaClassMetricKey.WOC, node);
+ return woc < WOC_LEVEL;
+ }
+
+
+ private boolean classRevealsDataAndLacksComplexity(ASTAnyTypeDeclaration node) {
+ int nopa = (int) JavaMetrics.get(JavaClassMetricKey.NOPA, node);
+ int noam = (int) JavaMetrics.get(JavaClassMetricKey.NOAM, node);
+ int wmc = (int) JavaMetrics.get(JavaClassMetricKey.WMC, node);
+
+ return nopa + noam > ACCESSOR_OR_FIELD_FEW_LEVEL && wmc < WMC_HIGH_LEVEL
+ || nopa + noam > ACCESSOR_OR_FIELD_MANY_LEVEL && wmc < WMC_VERY_HIGH_LEVEL;
+ }
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java
index 144945ab9b..2a6d615d8e 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java
@@ -8,6 +8,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
@@ -79,6 +81,8 @@ public final class JavaOperationSignature extends JavaSignature
+
+
+ Data Classes are simple data holders, which reveal most of their state, and
+ without complex functionality. The lack of functionality may indicate that
+ their behaviour is defined elsewhere, which is a sign of poor data-behaviour
+ proximity. By directly exposing their internals, Data Classes break encapsulation,
+ and therefore reduce the system's maintainability and understandability. Moreover,
+ classes tend to strongly rely on their data representation, which makes for a brittle
+ design.
+
+ Refactoring a Data Class should focus on restoring a good data-behaviour proximity. In
+ most cases, that means moving the operations defined on the data back into the class.
+ In some other cases it may make sense to remove entirely the class and move the data
+ into the former client classes.
+
+ 3
+
+
+
+
+
+
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java
index 631725eea7..6092bd905a 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java
@@ -126,16 +126,40 @@ public abstract class AbstractMetricTestRule extends AbstractJavaMetricsRule {
}
+ /** Gets a string representation rounded to the nearest half. */
+ private String presentableString(double val) {
+ boolean isInt = Math.floor(val) == val;
+
+ if (!isInt && val >= 0 && val <= 1) { // percentage
+ return roundedString(100 * val) + "%";
+ } else if (!isInt) {
+ return String.valueOf(roundedString(val));
+ } else {
+ return String.valueOf((int) val);
+ }
+ }
+
+
+ private String roundedString(double val) {
+ double truncated = Math.floor(100 * val) / 100;
+ if (truncated == Math.floor(truncated)) {
+ return String.valueOf((int) truncated);
+ } else {
+ return String.valueOf(truncated);
+ }
+ }
+
+
@Override
public Object visit(ASTAnyTypeDeclaration node, Object data) {
if (classKey != null && reportClasses && classKey.supports(node)) {
- int classValue = (int) JavaMetrics.get(classKey, node, metricOptions);
+ double classValue = JavaMetrics.get(classKey, node, metricOptions);
- String valueReport = String.valueOf(classValue);
+ String valueReport = presentableString(classValue);
if (opKey != null) {
- int highest = (int) JavaMetrics.get(opKey, node, metricOptions, ResultOption.HIGHEST);
- valueReport += " highest " + highest;
+ double highest = JavaMetrics.get(opKey, node, metricOptions, ResultOption.HIGHEST);
+ valueReport += " highest " + presentableString(highest);
}
if (classValue >= reportLevel) {
addViolation(data, node, new String[] {node.getQualifiedName().toString(), valueReport, });
@@ -148,9 +172,10 @@ public abstract class AbstractMetricTestRule extends AbstractJavaMetricsRule {
@Override
public Object visit(ASTMethodOrConstructorDeclaration node, Object data) {
if (opKey != null && reportMethods && opKey.supports(node)) {
- int methodValue = (int) JavaMetrics.get(opKey, node, metricOptions);
+ double methodValue = JavaMetrics.get(opKey, node, metricOptions);
if (methodValue >= reportLevel) {
- addViolation(data, node, new String[] {node.getQualifiedName().toString(), "" + methodValue, });
+ addViolation(data, node, new String[] {node.getQualifiedName().toString(),
+ "" + presentableString(methodValue), });
}
}
return data;
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java
index b09a8a4b67..3ae9def4a4 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AllMetricsTest.java
@@ -33,6 +33,9 @@ public class AllMetricsTest extends SimpleAggregatorTst {
addRule(RULESET, "WmcTest");
addRule(RULESET, "LocTest");
addRule(RULESET, "NPathTest");
+ addRule(RULESET, "NopaTest");
+ addRule(RULESET, "NoamTest");
+ addRule(RULESET, "WocTest");
}
}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamTestRule.java
new file mode 100644
index 0000000000..eeec1f4851
--- /dev/null
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamTestRule.java
@@ -0,0 +1,26 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.impl;
+
+import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
+import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
+
+/**
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class NoamTestRule extends AbstractMetricTestRule {
+
+ @Override
+ protected JavaClassMetricKey getClassKey() {
+ return JavaClassMetricKey.NOAM;
+ }
+
+
+ @Override
+ protected JavaOperationMetricKey getOpKey() {
+ return null;
+ }
+}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaTestRule.java
new file mode 100644
index 0000000000..444f69de00
--- /dev/null
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaTestRule.java
@@ -0,0 +1,26 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.impl;
+
+import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
+import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
+
+/**
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class NopaTestRule extends AbstractMetricTestRule {
+
+ @Override
+ protected JavaClassMetricKey getClassKey() {
+ return JavaClassMetricKey.NOPA;
+ }
+
+
+ @Override
+ protected JavaOperationMetricKey getOpKey() {
+ return null;
+ }
+}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/WocTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/WocTestRule.java
new file mode 100644
index 0000000000..123f81bf45
--- /dev/null
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/WocTestRule.java
@@ -0,0 +1,26 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.metrics.impl;
+
+import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
+import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
+
+/**
+ * @author Clément Fournier
+ * @since 6.0.0
+ */
+public class WocTestRule extends AbstractMetricTestRule {
+
+ @Override
+ protected JavaClassMetricKey getClassKey() {
+ return JavaClassMetricKey.WOC;
+ }
+
+
+ @Override
+ protected JavaOperationMetricKey getOpKey() {
+ return null;
+ }
+}
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java
index 22148254aa..9c6c90d235 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java
@@ -28,5 +28,6 @@ public class MetricsRulesTest extends SimpleAggregatorTst {
addRule(RULESET, "CyclomaticComplexity");
addRule(RULESET, "NcssCount");
addRule(RULESET, "NPathComplexity");
+ addRule(RULESET, "DataClass");
}
}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NoamTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NoamTest.xml
new file mode 100644
index 0000000000..653da8018a
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NoamTest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+ Full example
+ 1
+
+ '.Foo' has value 3.
+
+
+
+
+
+ Test empty class
+ 1
+
+ '.Foo' has value 0.
+
+
+
+
+
+ NOPA doesn't support enums, interfaces or annotations
+ 0
+
+
+
+
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NopaTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NopaTest.xml
new file mode 100644
index 0000000000..4aca132041
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NopaTest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+ Full example
+ 1
+
+ '.Foo' has value 7.
+
+
+
+
+
+ Test empty class
+ 1
+
+ '.Foo' has value 0.
+
+
+
+
+
+ NOPA doesn't support enums, interfaces or annotations
+ 0
+
+
+
+
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WocTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WocTest.xml
new file mode 100644
index 0000000000..303509b4ed
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WocTest.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ Full example
+ 1
+
+ '.Property' has value 11.11%.
+
+
+
+
+
+ Test empty class
+ 0
+
+
+
+
+ NOPA doesn't support enums, interfaces or annotations
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml
new file mode 100644
index 0000000000..3a73eb3c0f
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+ ArgoUML data class
+ 1
+
+ The class 'Property' is suspected to be a Data Class
+
+
+
+
+
+
+ expectedMessages = new ArrayList<>();
+ private List expectedLineNumbers = new ArrayList<>();
+ private String code;
+ private LanguageVersion languageVersion;
+ // default, avoids unintentional mixing of state between test cases
+ private boolean reinitializeRule = true;
+ private boolean isRegressionTest = true;
+ private boolean useAuxClasspath = true;
+ private int numberInDocument = -1;
+
+ // Empty descriptor added to please mvn surefire plugin
+ public TestDescriptor() {
+
+ }
+
+ public TestDescriptor(String code, String description, int numberOfProblemsExpected, Rule rule) {
+ this(code, description, numberOfProblemsExpected, rule, rule.getLanguage().getDefaultVersion());
+ }
+
+ public TestDescriptor(String code, String description, int numberOfProblemsExpected, Rule rule,
+ LanguageVersion languageVersion) {
+ this.rule = rule;
+ this.code = code;
+ this.description = description;
+ this.numberOfProblemsExpected = numberOfProblemsExpected;
+ this.languageVersion = languageVersion;
+ }
+
+ public int getNumberInDocument() {
+ return numberInDocument;
+ }
+
+ public void setNumberInDocument(int numberInDocument) {
+ this.numberInDocument = numberInDocument;
+ }
+
+ public void setExpectedMessages(List messages) {
+ expectedMessages.clear();
+ expectedMessages.addAll(messages);
+ }
+
+ public List getExpectedMessages() {
+ return expectedMessages;
+ }
+
+ public void setExpectedLineNumbers(List expectedLineNumbers) {
+ this.expectedLineNumbers.clear();
+ this.expectedLineNumbers.addAll(expectedLineNumbers);
+ }
+
+ public List getExpectedLineNumbers() {
+ return expectedLineNumbers;
+ }
+
+ public void setProperties(Properties properties) {
+ this.properties = properties;
+ }
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public LanguageVersion getLanguageVersion() {
+ return languageVersion;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getNumberOfProblemsExpected() {
+ return numberOfProblemsExpected;
+ }
+
+ public Rule getRule() {
+ return rule;
+ }
+
+ public boolean getReinitializeRule() {
+ return reinitializeRule;
+ }
+
+ public void setReinitializeRule(boolean reinitializeRule) {
+ this.reinitializeRule = reinitializeRule;
+ }
+
+ /**
+ * Checks whether we are testing for regression problems only. Return value
+ * is based on the system property "pmd.regress".
+ *
+ * @return false
if system property "pmd.regress" is set to
+ * false
, true
otherwise
+ */
+ public static boolean inRegressionTestMode() {
+ boolean inRegressionMode = true; // default
+ try {
+ // get the "pmd.regress" System property
+ String property = System.getProperty("pmd.regress");
+ if (property != null) {
+ inRegressionMode = Boolean.parseBoolean(property);
+ }
+ } catch (IllegalArgumentException e) {
+ }
+
+ return inRegressionMode;
+ }
+
+ public boolean isRegressionTest() {
+ return isRegressionTest;
+ }
+
+ public void setRegressionTest(boolean isRegressionTest) {
+ this.isRegressionTest = isRegressionTest;
+ }
+
+ public void setUseAuxClasspath(boolean useAuxClasspath) {
+ this.useAuxClasspath = useAuxClasspath;
+ }
+
+ public boolean isUseAuxClasspath() {
+ return useAuxClasspath;
+ }
+}
+ ]]>
+
+
+
+ PMD data class
+ 1
+
+ The class 'TestDescriptor' is suspected to be a Data Class
+
+
+
+
+
\ No newline at end of file
diff --git a/pmd-java/src/test/resources/rulesets/java/metrics_test.xml b/pmd-java/src/test/resources/rulesets/java/metrics_test.xml
index d3b6ecd4c7..e6424a61a7 100644
--- a/pmd-java/src/test/resources/rulesets/java/metrics_test.xml
+++ b/pmd-java/src/test/resources/rulesets/java/metrics_test.xml
@@ -39,4 +39,22 @@
metrics="true">
+
+
+
+
+
+
+
+
+