Skip to content

Commit bedaa2c

Browse files
#45 Adding code to execute the CouplingBetweenObjects rule
Adding: Slightly modified version of PMD's CouplingBetweenObjects rule Runner to execute the CouplingBetweenObjects rule Code to test the CouplginBetweenObjects rule runner classes.
1 parent 7569062 commit bedaa2c

File tree

6 files changed

+1345
-0
lines changed

6 files changed

+1345
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.hjug.metrics;
2+
3+
import lombok.Data;
4+
5+
import java.util.Scanner;
6+
7+
/**
8+
* Created by Jim on 11/16/2016.
9+
*/
10+
@Data
11+
public class CBOClass {
12+
13+
private String className;
14+
private String fileName;
15+
private String packageName;
16+
17+
private Integer couplingCount;
18+
19+
public CBOClass(String className, String fileName, String packageName, String result) {
20+
this.className = className;
21+
this.fileName = fileName;
22+
this.packageName = packageName;
23+
24+
try (Scanner scanner = new Scanner(result)) {
25+
couplingCount = scanner.useDelimiter("[^\\d]+").nextInt();
26+
}
27+
}
28+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.hjug.metrics;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import net.sourceforge.pmd.*;
5+
import net.sourceforge.pmd.lang.Language;
6+
import net.sourceforge.pmd.lang.LanguageRegistry;
7+
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
8+
import org.hjug.metrics.rules.CBORule;
9+
10+
import java.io.File;
11+
import java.io.FileInputStream;
12+
import java.io.FileNotFoundException;
13+
import java.io.InputStream;
14+
import java.util.Optional;
15+
16+
// based on http://sdoulger.blogspot.com/2010/12/call-pmd-from-your-code-with-you-custom.html
17+
@Slf4j
18+
public class CBORuleRunner {
19+
20+
private SourceCodeProcessor sourceCodeProcessor;
21+
private RuleSets ruleSets;
22+
private Language java = LanguageRegistry.getLanguage(JavaLanguageModule.NAME);
23+
24+
public CBORuleRunner() {
25+
PMD pmd = new PMD();
26+
sourceCodeProcessor = pmd.getSourceCodeProcessor();
27+
28+
Rule cboClassRule = new CBORule();
29+
cboClassRule.setLanguage(java);
30+
31+
// add your rule to the ruleset
32+
RuleSetFactory ruleSetFactory = new RuleSetFactory();
33+
RuleSet ruleSet2 = ruleSetFactory.createSingleRuleRuleSet(cboClassRule);
34+
35+
ruleSets = new RuleSets(ruleSet2);
36+
}
37+
38+
public Optional<CBOClass> runCBOClassRule(File file) {
39+
// TODO: Capture file path and file ref?
40+
return runPMD(file);
41+
}
42+
43+
public Optional<CBOClass> runCBOClassRule(String name, InputStream fis) {
44+
return runPMD(name, fis);
45+
}
46+
47+
/**
48+
* Runs PMD on the specific file with the specific rule.
49+
* @param file target file
50+
* @return List with errors. If empty then no error
51+
*/
52+
public Optional<CBOClass> runPMD(File file) {
53+
54+
FileInputStream fis = null;
55+
try {
56+
fis = new FileInputStream(file);
57+
} catch (FileNotFoundException ignore) {
58+
log.warn("{} was not found", file.getName());
59+
}
60+
61+
return runPMD(file.getName(), fis);
62+
}
63+
64+
public Optional<CBOClass> runPMD(String sourceCodeFileName, InputStream inputStream) {
65+
CBOClass cboClass = null;
66+
try {
67+
// Set the javaVersion you are using. (*1)
68+
// pmd.setJavaVersion(SourceType.JAVA_16); -- MAY NEED TO SPECIFY THIS...
69+
// Get a context and initialize it with The Report that PMD will return
70+
final RuleContext ctx = new RuleContext();
71+
ctx.setReport(new Report());
72+
// target filename
73+
ctx.setSourceCodeFile(new File(sourceCodeFileName));
74+
sourceCodeProcessor.processSourceCode(inputStream, ruleSets, ctx);
75+
76+
// write results
77+
if (!ctx.getReport().isEmpty()) {
78+
for (final RuleViolation violation : ctx.getReport()) {
79+
cboClass = new CBOClass(
80+
violation.getClassName(),
81+
sourceCodeFileName,
82+
violation.getPackageName(),
83+
violation.getDescription());
84+
}
85+
}
86+
} catch (PMDException ignore) {
87+
log.warn("runPMD failed", ignore);
88+
}
89+
return Optional.ofNullable(cboClass);
90+
}
91+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package org.hjug.metrics.rules;
2+
3+
import net.sourceforge.pmd.lang.ast.Node;
4+
import net.sourceforge.pmd.lang.java.ast.*;
5+
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
6+
import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
7+
import net.sourceforge.pmd.properties.PropertyBuilder;
8+
import net.sourceforge.pmd.properties.PropertyDescriptor;
9+
import net.sourceforge.pmd.properties.PropertyFactory;
10+
import net.sourceforge.pmd.properties.constraints.NumericConstraints;
11+
12+
import java.util.HashSet;
13+
import java.util.List;
14+
import java.util.Set;
15+
16+
/**
17+
* Copy of PMD's CouplingBetweenObjectsRule
18+
* but generates the originally intended message containing coupling count
19+
*/
20+
public class CBORule extends AbstractJavaRule {
21+
private int couplingCount;
22+
private Set<String> typesFoundSoFar;
23+
private static final PropertyDescriptor<Integer> THRESHOLD_DESCRIPTOR = ((PropertyBuilder.GenericPropertyBuilder)((PropertyBuilder.GenericPropertyBuilder)((PropertyBuilder.GenericPropertyBuilder) PropertyFactory.intProperty("threshold").desc("Unique type reporting threshold")).require(NumericConstraints.positive())).defaultValue(20)).build();
24+
25+
public CBORule() {
26+
this.definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
27+
}
28+
29+
public Object visit(ASTCompilationUnit cu, Object data) {
30+
this.typesFoundSoFar = new HashSet();
31+
this.couplingCount = 0;
32+
Object returnObj = super.visit(cu, data);
33+
if (this.couplingCount > (Integer)this.getProperty(THRESHOLD_DESCRIPTOR)) {
34+
//only the line below is different from PMD's CouplingBetweenObjectsRule class
35+
this.addViolationWithMessage(data, cu, "A value of " + this.couplingCount + " may denote a high amount of coupling within the class");
36+
}
37+
38+
return returnObj;
39+
}
40+
41+
public Object visit(ASTResultType node, Object data) {
42+
for(int x = 0; x < node.getNumChildren(); ++x) {
43+
Node tNode = node.getChild(x);
44+
if (tNode instanceof ASTType) {
45+
Node reftypeNode = tNode.getChild(0);
46+
if (reftypeNode instanceof ASTReferenceType) {
47+
Node classOrIntType = reftypeNode.getChild(0);
48+
if (classOrIntType instanceof ASTClassOrInterfaceType) {
49+
this.checkVariableType(classOrIntType, classOrIntType.getImage());
50+
}
51+
}
52+
}
53+
}
54+
55+
return super.visit(node, data);
56+
}
57+
58+
public Object visit(ASTLocalVariableDeclaration node, Object data) {
59+
this.handleASTTypeChildren(node);
60+
return super.visit(node, data);
61+
}
62+
63+
public Object visit(ASTFormalParameter node, Object data) {
64+
this.handleASTTypeChildren(node);
65+
return super.visit(node, data);
66+
}
67+
68+
public Object visit(ASTFieldDeclaration node, Object data) {
69+
for(int x = 0; x < node.getNumChildren(); ++x) {
70+
Node firstStmt = node.getChild(x);
71+
if (firstStmt instanceof ASTType) {
72+
ASTType tp = (ASTType)firstStmt;
73+
Node nd = tp.getChild(0);
74+
this.checkVariableType(nd, nd.getImage());
75+
}
76+
}
77+
78+
return super.visit(node, data);
79+
}
80+
81+
private void handleASTTypeChildren(Node node) {
82+
for(int x = 0; x < node.getNumChildren(); ++x) {
83+
Node sNode = node.getChild(x);
84+
if (sNode instanceof ASTType) {
85+
Node nameNode = sNode.getChild(0);
86+
this.checkVariableType(nameNode, nameNode.getImage());
87+
}
88+
}
89+
90+
}
91+
92+
private void checkVariableType(Node nameNode, String variableType) {
93+
List<ASTClassOrInterfaceDeclaration> parentTypes = nameNode.getParentsOfType(ASTClassOrInterfaceDeclaration.class);
94+
if (!parentTypes.isEmpty()) {
95+
if (!((ASTClassOrInterfaceDeclaration)parentTypes.get(0)).isInterface()) {
96+
ClassScope clzScope = (ClassScope)((JavaNode)nameNode).getScope().getEnclosingScope(ClassScope.class);
97+
if (!clzScope.getClassName().equals(variableType) && !this.filterTypes(variableType) && !this.typesFoundSoFar.contains(variableType)) {
98+
++this.couplingCount;
99+
this.typesFoundSoFar.add(variableType);
100+
}
101+
102+
}
103+
}
104+
}
105+
106+
private boolean filterTypes(String variableType) {
107+
return variableType != null && (variableType.startsWith("java.lang.") || "String".equals(variableType) || this.filterPrimitivesAndWrappers(variableType));
108+
}
109+
110+
private boolean filterPrimitivesAndWrappers(String variableType) {
111+
return "int".equals(variableType) || "Integer".equals(variableType) || "char".equals(variableType) || "Character".equals(variableType) || "double".equals(variableType) || "long".equals(variableType) || "short".equals(variableType) || "float".equals(variableType) || "byte".equals(variableType) || "boolean".equals(variableType);
112+
}
113+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.hjug.metrics;
2+
3+
import org.junit.After;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
7+
import java.util.Locale;
8+
9+
import static org.junit.Assert.assertEquals;
10+
11+
public class CBOClassParsingTest {
12+
13+
private Locale defaultLocale;
14+
15+
@Before
16+
public void before() {
17+
defaultLocale = Locale.getDefault(Locale.Category.FORMAT);
18+
Locale.setDefault(Locale.Category.FORMAT, Locale.ENGLISH);
19+
}
20+
21+
@After
22+
public void after() {
23+
Locale.setDefault(defaultLocale);
24+
}
25+
26+
@Test
27+
public void test() {
28+
String result = "A value of 20 may denote a high amount of coupling within the class";
29+
CBOClass cboClass = new CBOClass("a", "a.txt", "org.hjug", result);
30+
assertEquals(Integer.valueOf(20), cboClass.getCouplingCount());
31+
}
32+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.hjug.metrics;
2+
3+
import org.junit.Assert;
4+
import org.junit.Before;
5+
import org.junit.Ignore;
6+
import org.junit.Test;
7+
8+
import java.util.Optional;
9+
10+
@Ignore
11+
public class CBORuleRunnerTest {
12+
13+
private CBORuleRunner CBORuleRunner;
14+
15+
@Before
16+
public void setUp() {
17+
CBORuleRunner = new CBORuleRunner();
18+
}
19+
20+
@Test
21+
public void testRuleRunnerExpectOneClass() throws Exception {
22+
String attributeHandler = "Console.java";
23+
Optional<CBOClass> optionalResult = CBORuleRunner.runCBOClassRule(
24+
attributeHandler, getClass().getClassLoader().getResourceAsStream(attributeHandler));
25+
26+
CBOClass result = optionalResult.get();
27+
28+
Assert.assertEquals("Console.java", result.getFileName());
29+
Assert.assertEquals("io.confluent.ksql.cli.console", result.getPackageName());
30+
Assert.assertEquals(35, result.getCouplingCount().longValue());
31+
}
32+
33+
@Test
34+
public void testRuleRunnerExpectNoResults() throws Exception {
35+
String attributeHandler = "Attributes.java";
36+
Optional<CBOClass> result = CBORuleRunner.runCBOClassRule(
37+
attributeHandler, getClass().getClassLoader().getResourceAsStream(attributeHandler));
38+
39+
Assert.assertFalse(result.isPresent());
40+
}
41+
}

0 commit comments

Comments
 (0)