Skip to content

Commit 0c4e98f

Browse files
Merge pull request #96 from refactorfirst/weighted-edges
#92 Add weighted edges
2 parents 480aec4 + ea817cd commit 0c4e98f

File tree

9 files changed

+93
-66
lines changed

9 files changed

+93
-66
lines changed

circular-reference-detector/src/main/java/org/hjug/app/CircularReferenceDetectorApp.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree;
1111
import org.jgrapht.graph.AsSubgraph;
1212
import org.jgrapht.graph.AsUndirectedGraph;
13-
import org.jgrapht.graph.DefaultEdge;
13+
import org.jgrapht.graph.DefaultWeightedEdge;
1414

1515
/**
1616
* Command line application to detect circular references in a java project.
@@ -41,7 +41,7 @@ public void launchApp(String[] args) {
4141
String outputDirectoryPath = args[1];
4242
JavaProjectParser javaProjectParser = new JavaProjectParser();
4343
try {
44-
Graph<String, DefaultEdge> classReferencesGraph =
44+
Graph<String, DefaultWeightedEdge> classReferencesGraph =
4545
javaProjectParser.getClassReferences(srcDirectoryPath);
4646
detectAndStoreCyclesInDirectory(outputDirectoryPath, classReferencesGraph);
4747
} catch (Exception e) {
@@ -51,9 +51,9 @@ public void launchApp(String[] args) {
5151
}
5252

5353
private void detectAndStoreCyclesInDirectory(
54-
String outputDirectoryPath, Graph<String, DefaultEdge> classReferencesGraph) {
54+
String outputDirectoryPath, Graph<String, DefaultWeightedEdge> classReferencesGraph) {
5555
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
56-
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
56+
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
5757
circularReferenceChecker.detectCycles(classReferencesGraph);
5858
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
5959
try {
@@ -64,13 +64,13 @@ private void detectAndStoreCyclesInDirectory(
6464
renderedSubGraphs.put(vertex, subGraph);
6565
System.out.println(
6666
"Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
67-
GusfieldGomoryHuCutTree<String, DefaultEdge> gusfieldGomoryHuCutTree =
67+
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
6868
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
6969
double minCut = gusfieldGomoryHuCutTree.calculateMinCut();
7070
System.out.println("Min cut weight: " + minCut);
71-
Set<DefaultEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
71+
Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
7272
System.out.println("Minimum Cut Edges:");
73-
for (DefaultEdge minCutEdge : minCutEdges) {
73+
for (DefaultWeightedEdge minCutEdge : minCutEdges) {
7474
System.out.println(minCutEdge);
7575
}
7676
}
@@ -80,7 +80,7 @@ private void detectAndStoreCyclesInDirectory(
8080
});
8181
}
8282

83-
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultEdge> subGraph, String vertex) {
83+
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) {
8484
if (!renderedSubGraphs.isEmpty()) {
8585
for (AsSubgraph renderedSubGraph : renderedSubGraphs.values()) {
8686
if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size()

circular-reference-detector/src/main/java/org/hjug/cycledetector/CircularReferenceChecker.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import org.jgrapht.alg.cycle.CycleDetector;
1515
import org.jgrapht.ext.JGraphXAdapter;
1616
import org.jgrapht.graph.AsSubgraph;
17-
import org.jgrapht.graph.DefaultEdge;
17+
import org.jgrapht.graph.DefaultWeightedEdge;
1818

1919
public class CircularReferenceChecker {
2020

@@ -25,11 +25,12 @@ public class CircularReferenceChecker {
2525
* @param classReferencesGraph
2626
* @return a Map of Class and its Cycle Graph
2727
*/
28-
public Map<String, AsSubgraph<String, DefaultEdge>> detectCycles(Graph<String, DefaultEdge> classReferencesGraph) {
29-
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap = new HashMap<>();
30-
CycleDetector<String, DefaultEdge> cycleDetector = new CycleDetector<>(classReferencesGraph);
28+
public Map<String, AsSubgraph<String, DefaultWeightedEdge>> detectCycles(
29+
Graph<String, DefaultWeightedEdge> classReferencesGraph) {
30+
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap = new HashMap<>();
31+
CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(classReferencesGraph);
3132
cycleDetector.findCycles().forEach(v -> {
32-
AsSubgraph<String, DefaultEdge> subGraph =
33+
AsSubgraph<String, DefaultWeightedEdge> subGraph =
3334
new AsSubgraph<>(classReferencesGraph, cycleDetector.findCyclesContainingVertex(v));
3435
cyclesForEveryVertexMap.put(v, subGraph);
3536
});
@@ -45,12 +46,12 @@ public Map<String, AsSubgraph<String, DefaultEdge>> detectCycles(Graph<String, D
4546
* @param imageName
4647
* @throws IOException
4748
*/
48-
public void createImage(String outputDirectoryPath, Graph<String, DefaultEdge> subGraph, String imageName)
49+
public void createImage(String outputDirectoryPath, Graph<String, DefaultWeightedEdge> subGraph, String imageName)
4950
throws IOException {
5051
new File(outputDirectoryPath).mkdirs();
5152
File imgFile = new File(outputDirectoryPath + "/graph" + imageName + ".png");
5253
if (imgFile.createNewFile()) {
53-
JGraphXAdapter<String, DefaultEdge> graphAdapter = new JGraphXAdapter<>(subGraph);
54+
JGraphXAdapter<String, DefaultWeightedEdge> graphAdapter = new JGraphXAdapter<>(subGraph);
5455
mxIGraphLayout layout = new mxCircleLayout(graphAdapter);
5556
layout.execute(graphAdapter.getDefaultParent());
5657

circular-reference-detector/src/main/java/org/hjug/parser/JavaProjectParser.java

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import java.util.stream.Collectors;
1616
import java.util.stream.Stream;
1717
import org.jgrapht.Graph;
18-
import org.jgrapht.graph.DefaultDirectedGraph;
19-
import org.jgrapht.graph.DefaultEdge;
18+
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
19+
import org.jgrapht.graph.DefaultWeightedEdge;
2020

2121
public class JavaProjectParser {
2222

@@ -26,8 +26,9 @@ public class JavaProjectParser {
2626
* @return
2727
* @throws IOException
2828
*/
29-
public Graph<String, DefaultEdge> getClassReferences(String srcDirectory) throws IOException {
30-
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
29+
public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory) throws IOException {
30+
Graph<String, DefaultWeightedEdge> classReferencesGraph =
31+
new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class);
3132
if (srcDirectory == null || srcDirectory.isEmpty()) {
3233
throw new IllegalArgumentException();
3334
} else {
@@ -36,20 +37,29 @@ public Graph<String, DefaultEdge> getClassReferences(String srcDirectory) throws
3637
filesStream
3738
.filter(path -> path.getFileName().toString().endsWith(".java"))
3839
.forEach(path -> {
39-
Set<String> varTypes = getInstanceVarTypes(classNames, path.toFile());
40-
varTypes.addAll(getMethodTypes(classNames, path.toFile()));
41-
if (!varTypes.isEmpty()) {
40+
List<String> types = getInstanceVarTypes(classNames, path.toFile());
41+
types.addAll(getMethodArgumentTypes(classNames, path.toFile()));
42+
if (!types.isEmpty()) {
4243
String className =
4344
getClassName(path.getFileName().toString());
4445
classReferencesGraph.addVertex(className);
45-
varTypes.forEach(classReferencesGraph::addVertex);
46-
varTypes.forEach(var -> classReferencesGraph.addEdge(className, var));
46+
types.forEach(classReferencesGraph::addVertex);
47+
types.forEach(type -> {
48+
if (!classReferencesGraph.containsEdge(className, type)) {
49+
classReferencesGraph.addEdge(className, type);
50+
} else {
51+
DefaultWeightedEdge edge = classReferencesGraph.getEdge(className, type);
52+
classReferencesGraph.setEdgeWeight(
53+
edge, classReferencesGraph.getEdgeWeight(edge) + 1);
54+
}
55+
});
4756
}
4857
});
4958
} catch (FileNotFoundException e) {
5059
e.printStackTrace();
5160
}
5261
}
62+
5363
return classReferencesGraph;
5464
}
5565

@@ -59,7 +69,7 @@ public Graph<String, DefaultEdge> getClassReferences(String srcDirectory) throws
5969
* @param file
6070
* @return
6171
*/
62-
private Set<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
72+
private List<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
6373
CompilationUnit compilationUnit;
6474
try {
6575
compilationUnit = StaticJavaParser.parse(javaSrcFile);
@@ -68,11 +78,11 @@ private Set<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File
6878
.filter(v -> !v.isPrimitiveType())
6979
.map(Object::toString)
7080
.filter(classNamesToFilterBy::contains)
71-
.collect(Collectors.toSet());
81+
.collect(Collectors.toList());
7282
} catch (FileNotFoundException e) {
7383
e.printStackTrace();
7484
}
75-
return new HashSet<>();
85+
return new ArrayList<>();
7686
}
7787

7888
/**
@@ -81,23 +91,23 @@ private Set<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File
8191
* @param file
8292
* @return
8393
*/
84-
private Set<String> getMethodTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
94+
private List<String> getMethodArgumentTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
8595
CompilationUnit compilationUnit;
8696
try {
8797
compilationUnit = StaticJavaParser.parse(javaSrcFile);
8898
return compilationUnit.findAll(MethodDeclaration.class).stream()
8999
.flatMap(f -> f.getParameters().stream()
90100
.map(Parameter::getType)
91101
.filter(type -> !type.isPrimitiveType())
92-
.collect(Collectors.toSet())
102+
.collect(Collectors.toList())
93103
.stream())
94104
.map(Object::toString)
95105
.filter(classNamesToFilterBy::contains)
96-
.collect(Collectors.toSet());
106+
.collect(Collectors.toList());
97107
} catch (FileNotFoundException e) {
98108
e.printStackTrace();
99109
}
100-
return new HashSet<>();
110+
return new ArrayList<>();
101111
}
102112

103113
/**

circular-reference-detector/src/test/java/org/hjug/cycledetector/CircularReferenceCheckerTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import org.jgrapht.Graph;
1010
import org.jgrapht.graph.AsSubgraph;
1111
import org.jgrapht.graph.DefaultDirectedGraph;
12-
import org.jgrapht.graph.DefaultEdge;
12+
import org.jgrapht.graph.DefaultWeightedEdge;
1313
import org.junit.jupiter.api.DisplayName;
1414
import org.junit.jupiter.api.Test;
1515

@@ -20,22 +20,22 @@ class CircularReferenceCheckerTests {
2020
@DisplayName("Detect 3 cycles from given graph.")
2121
@Test
2222
public void detectCyclesTest() {
23-
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
23+
Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class);
2424
classReferencesGraph.addVertex("A");
2525
classReferencesGraph.addVertex("B");
2626
classReferencesGraph.addVertex("C");
2727
classReferencesGraph.addEdge("A", "B");
2828
classReferencesGraph.addEdge("B", "C");
2929
classReferencesGraph.addEdge("C", "A");
30-
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
30+
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
3131
sutCircularReferenceChecker.detectCycles(classReferencesGraph);
3232
assertEquals(3, cyclesForEveryVertexMap.size());
3333
}
3434

3535
@DisplayName("Create graph image in given outputDirectory")
3636
@Test
3737
public void createImageTest() throws IOException {
38-
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
38+
Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class);
3939
classReferencesGraph.addVertex("A");
4040
classReferencesGraph.addVertex("B");
4141
classReferencesGraph.addVertex("C");

circular-reference-detector/src/test/java/org/hjug/parser/JavaProjectParserTests.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.io.File;
66
import java.io.IOException;
77
import org.jgrapht.Graph;
8-
import org.jgrapht.graph.DefaultEdge;
8+
import org.jgrapht.graph.DefaultWeightedEdge;
99
import org.junit.jupiter.api.Assertions;
1010
import org.junit.jupiter.api.DisplayName;
1111
import org.junit.jupiter.api.Test;
@@ -16,16 +16,16 @@ class JavaProjectParserTests {
1616

1717
@DisplayName("When source directory input param is empty or null throw IllegalArgumentException.")
1818
@Test
19-
public void parseSourceDirectoryEmptyTest() {
19+
void parseSourceDirectoryEmptyTest() {
2020
Assertions.assertThrows(IllegalArgumentException.class, () -> sutJavaProjectParser.getClassReferences(""));
2121
Assertions.assertThrows(IllegalArgumentException.class, () -> sutJavaProjectParser.getClassReferences(null));
2222
}
2323

2424
@DisplayName("Given a valid source directory input parameter return a valid graph.")
2525
@Test
26-
public void parseSourceDirectoryTest() throws IOException {
26+
void parseSourceDirectoryTest() throws IOException {
2727
File srcDirectory = new File("src/test/resources/javaSrcDirectory");
28-
Graph<String, DefaultEdge> classReferencesGraph =
28+
Graph<String, DefaultWeightedEdge> classReferencesGraph =
2929
sutJavaProjectParser.getClassReferences(srcDirectory.getAbsolutePath());
3030
assertNotNull(classReferencesGraph);
3131
assertEquals(5, classReferencesGraph.vertexSet().size());
@@ -42,5 +42,14 @@ public void parseSourceDirectoryTest() throws IOException {
4242
assertTrue(classReferencesGraph.containsEdge("D", "A"));
4343
assertTrue(classReferencesGraph.containsEdge("D", "C"));
4444
assertTrue(classReferencesGraph.containsEdge("E", "D"));
45+
46+
// confirm edge weight calculations
47+
assertEquals(1, getEdgeWeight(classReferencesGraph, "A", "B"));
48+
assertEquals(2, getEdgeWeight(classReferencesGraph, "E", "D"));
49+
}
50+
51+
private static double getEdgeWeight(
52+
Graph<String, DefaultWeightedEdge> classReferencesGraph, String sourceVertex, String targetVertex) {
53+
return classReferencesGraph.getEdgeWeight(classReferencesGraph.getEdge(sourceVertex, targetVertex));
4554
}
4655
}

circular-reference-detector/src/test/resources/javaSrcDirectory/com/ideacrest/parser/testclasses/E.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
public class E {
44
D d;
5+
D d2;
56
}

cost-benefit-calculator/src/main/java/org/hjug/cbc/CostBenefitCalculator.java

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.*;
1414
import java.util.stream.Collectors;
1515
import java.util.stream.Stream;
16+
import lombok.Getter;
1617
import lombok.extern.slf4j.Slf4j;
1718
import net.sourceforge.pmd.*;
1819
import net.sourceforge.pmd.lang.LanguageRegistry;
@@ -29,12 +30,12 @@
2930
import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree;
3031
import org.jgrapht.graph.AsSubgraph;
3132
import org.jgrapht.graph.AsUndirectedGraph;
32-
import org.jgrapht.graph.DefaultEdge;
33+
import org.jgrapht.graph.DefaultWeightedEdge;
3334

3435
@Slf4j
3536
public class CostBenefitCalculator {
3637

37-
private final Map<String, AsSubgraph> renderedSubGraphs = new HashMap<>();
38+
private final Map<String, AsSubgraph<String, DefaultWeightedEdge>> renderedSubGraphs = new HashMap<>();
3839

3940
private Report report;
4041
private String repositoryPath;
@@ -43,6 +44,9 @@ public class CostBenefitCalculator {
4344
private final ChangePronenessRanker changePronenessRanker;
4445
private final JavaProjectParser javaProjectParser = new JavaProjectParser();
4546

47+
@Getter
48+
private Graph<String, DefaultWeightedEdge> classReferencesGraph;
49+
4650
public CostBenefitCalculator(String repositoryPath) {
4751
this.repositoryPath = repositoryPath;
4852

@@ -65,15 +69,15 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
6569
List<RankedCycle> rankedCycles = new ArrayList<>();
6670
try {
6771
Map<String, String> classNamesAndPaths = getClassNamesAndPaths();
68-
Graph<String, DefaultEdge> classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
72+
classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
6973
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
70-
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
74+
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
7175
circularReferenceChecker.detectCycles(classReferencesGraph);
7276
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
7377
int vertexCount = subGraph.vertexSet().size();
7478
int edgeCount = subGraph.edgeSet().size();
7579
double minCut = 0;
76-
Set<DefaultEdge> minCutEdges = null;
80+
Set<DefaultWeightedEdge> minCutEdges;
7781
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
7882
if (renderImages) {
7983
try {
@@ -86,17 +90,11 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
8690

8791
renderedSubGraphs.put(vertex, subGraph);
8892
log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
89-
GusfieldGomoryHuCutTree<String, DefaultEdge> gusfieldGomoryHuCutTree =
93+
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
9094
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
9195
minCut = gusfieldGomoryHuCutTree.calculateMinCut();
92-
log.info("Min cut weight: " + minCut);
9396
minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
9497

95-
log.info("Minimum Cut Edges:");
96-
for (DefaultEdge minCutEdge : minCutEdges) {
97-
log.info(minCutEdge.toString());
98-
}
99-
10098
List<CycleNode> cycleNodes = subGraph.vertexSet().stream()
10199
.map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle)))
102100
.collect(Collectors.toList());
@@ -109,8 +107,8 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
109107
}
110108

111109
for (ScmLogInfo changeRank : changeRanks) {
112-
CycleNode cn = cycleNodeMap.get(changeRank.getPath());
113-
cn.setScmLogInfo(changeRank);
110+
CycleNode cycleNode = cycleNodeMap.get(changeRank.getPath());
111+
cycleNode.setScmLogInfo(changeRank);
114112
}
115113

116114
// sum change proneness ranks
@@ -148,9 +146,9 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
148146
return rankedCycles;
149147
}
150148

151-
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultEdge> subGraph, String vertex) {
149+
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) {
152150
if (!renderedSubGraphs.isEmpty()) {
153-
for (AsSubgraph renderedSubGraph : renderedSubGraphs.values()) {
151+
for (AsSubgraph<String, DefaultWeightedEdge> renderedSubGraph : renderedSubGraphs.values()) {
154152
if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size()
155153
&& renderedSubGraph.edgeSet().size()
156154
== subGraph.edgeSet().size()

0 commit comments

Comments
 (0)