Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.graph.AsUndirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultWeightedEdge;

/**
* Command line application to detect circular references in a java project.
Expand Down Expand Up @@ -41,7 +41,7 @@ public void launchApp(String[] args) {
String outputDirectoryPath = args[1];
JavaProjectParser javaProjectParser = new JavaProjectParser();
try {
Graph<String, DefaultEdge> classReferencesGraph =
Graph<String, DefaultWeightedEdge> classReferencesGraph =
javaProjectParser.getClassReferences(srcDirectoryPath);
detectAndStoreCyclesInDirectory(outputDirectoryPath, classReferencesGraph);
} catch (Exception e) {
Expand All @@ -51,9 +51,9 @@ public void launchApp(String[] args) {
}

private void detectAndStoreCyclesInDirectory(
String outputDirectoryPath, Graph<String, DefaultEdge> classReferencesGraph) {
String outputDirectoryPath, Graph<String, DefaultWeightedEdge> classReferencesGraph) {
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
circularReferenceChecker.detectCycles(classReferencesGraph);
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
try {
Expand All @@ -64,13 +64,13 @@ private void detectAndStoreCyclesInDirectory(
renderedSubGraphs.put(vertex, subGraph);
System.out.println(
"Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
GusfieldGomoryHuCutTree<String, DefaultEdge> gusfieldGomoryHuCutTree =
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
double minCut = gusfieldGomoryHuCutTree.calculateMinCut();
System.out.println("Min cut weight: " + minCut);
Set<DefaultEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
Set<DefaultWeightedEdge> minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();
System.out.println("Minimum Cut Edges:");
for (DefaultEdge minCutEdge : minCutEdges) {
for (DefaultWeightedEdge minCutEdge : minCutEdges) {
System.out.println(minCutEdge);
}
}
Expand All @@ -80,7 +80,7 @@ private void detectAndStoreCyclesInDirectory(
});
}

private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultEdge> subGraph, String vertex) {
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) {
if (!renderedSubGraphs.isEmpty()) {
for (AsSubgraph renderedSubGraph : renderedSubGraphs.values()) {
if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import org.jgrapht.alg.cycle.CycleDetector;
import org.jgrapht.ext.JGraphXAdapter;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultWeightedEdge;

public class CircularReferenceChecker {

Expand All @@ -25,11 +25,12 @@ public class CircularReferenceChecker {
* @param classReferencesGraph
* @return a Map of Class and its Cycle Graph
*/
public Map<String, AsSubgraph<String, DefaultEdge>> detectCycles(Graph<String, DefaultEdge> classReferencesGraph) {
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap = new HashMap<>();
CycleDetector<String, DefaultEdge> cycleDetector = new CycleDetector<>(classReferencesGraph);
public Map<String, AsSubgraph<String, DefaultWeightedEdge>> detectCycles(
Graph<String, DefaultWeightedEdge> classReferencesGraph) {
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap = new HashMap<>();
CycleDetector<String, DefaultWeightedEdge> cycleDetector = new CycleDetector<>(classReferencesGraph);
cycleDetector.findCycles().forEach(v -> {
AsSubgraph<String, DefaultEdge> subGraph =
AsSubgraph<String, DefaultWeightedEdge> subGraph =
new AsSubgraph<>(classReferencesGraph, cycleDetector.findCyclesContainingVertex(v));
cyclesForEveryVertexMap.put(v, subGraph);
});
Expand All @@ -45,12 +46,12 @@ public Map<String, AsSubgraph<String, DefaultEdge>> detectCycles(Graph<String, D
* @param imageName
* @throws IOException
*/
public void createImage(String outputDirectoryPath, Graph<String, DefaultEdge> subGraph, String imageName)
public void createImage(String outputDirectoryPath, Graph<String, DefaultWeightedEdge> subGraph, String imageName)
throws IOException {
new File(outputDirectoryPath).mkdirs();
File imgFile = new File(outputDirectoryPath + "/graph" + imageName + ".png");
if (imgFile.createNewFile()) {
JGraphXAdapter<String, DefaultEdge> graphAdapter = new JGraphXAdapter<>(subGraph);
JGraphXAdapter<String, DefaultWeightedEdge> graphAdapter = new JGraphXAdapter<>(subGraph);
mxIGraphLayout layout = new mxCircleLayout(graphAdapter);
layout.execute(graphAdapter.getDefaultParent());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.graph.DefaultWeightedEdge;

public class JavaProjectParser {

Expand All @@ -26,8 +26,9 @@ public class JavaProjectParser {
* @return
* @throws IOException
*/
public Graph<String, DefaultEdge> getClassReferences(String srcDirectory) throws IOException {
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
public Graph<String, DefaultWeightedEdge> getClassReferences(String srcDirectory) throws IOException {
Graph<String, DefaultWeightedEdge> classReferencesGraph =
new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class);
if (srcDirectory == null || srcDirectory.isEmpty()) {
throw new IllegalArgumentException();
} else {
Expand All @@ -36,20 +37,29 @@ public Graph<String, DefaultEdge> getClassReferences(String srcDirectory) throws
filesStream
.filter(path -> path.getFileName().toString().endsWith(".java"))
.forEach(path -> {
Set<String> varTypes = getInstanceVarTypes(classNames, path.toFile());
varTypes.addAll(getMethodTypes(classNames, path.toFile()));
if (!varTypes.isEmpty()) {
List<String> types = getInstanceVarTypes(classNames, path.toFile());
types.addAll(getMethodArgumentTypes(classNames, path.toFile()));
if (!types.isEmpty()) {
String className =
getClassName(path.getFileName().toString());
classReferencesGraph.addVertex(className);
varTypes.forEach(classReferencesGraph::addVertex);
varTypes.forEach(var -> classReferencesGraph.addEdge(className, var));
types.forEach(classReferencesGraph::addVertex);
types.forEach(type -> {
if (!classReferencesGraph.containsEdge(className, type)) {
classReferencesGraph.addEdge(className, type);
} else {
DefaultWeightedEdge edge = classReferencesGraph.getEdge(className, type);
classReferencesGraph.setEdgeWeight(
edge, classReferencesGraph.getEdgeWeight(edge) + 1);
}
});
}
});
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}

return classReferencesGraph;
}

Expand All @@ -59,7 +69,7 @@ public Graph<String, DefaultEdge> getClassReferences(String srcDirectory) throws
* @param file
* @return
*/
private Set<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
private List<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
CompilationUnit compilationUnit;
try {
compilationUnit = StaticJavaParser.parse(javaSrcFile);
Expand All @@ -68,11 +78,11 @@ private Set<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File
.filter(v -> !v.isPrimitiveType())
.map(Object::toString)
.filter(classNamesToFilterBy::contains)
.collect(Collectors.toSet());
.collect(Collectors.toList());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return new HashSet<>();
return new ArrayList<>();
}

/**
Expand All @@ -81,23 +91,23 @@ private Set<String> getInstanceVarTypes(List<String> classNamesToFilterBy, File
* @param file
* @return
*/
private Set<String> getMethodTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
private List<String> getMethodArgumentTypes(List<String> classNamesToFilterBy, File javaSrcFile) {
CompilationUnit compilationUnit;
try {
compilationUnit = StaticJavaParser.parse(javaSrcFile);
return compilationUnit.findAll(MethodDeclaration.class).stream()
.flatMap(f -> f.getParameters().stream()
.map(Parameter::getType)
.filter(type -> !type.isPrimitiveType())
.collect(Collectors.toSet())
.collect(Collectors.toList())
.stream())
.map(Object::toString)
.filter(classNamesToFilterBy::contains)
.collect(Collectors.toSet());
.collect(Collectors.toList());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return new HashSet<>();
return new ArrayList<>();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.jgrapht.Graph;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand All @@ -20,22 +20,22 @@ class CircularReferenceCheckerTests {
@DisplayName("Detect 3 cycles from given graph.")
@Test
public void detectCyclesTest() {
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class);
classReferencesGraph.addVertex("A");
classReferencesGraph.addVertex("B");
classReferencesGraph.addVertex("C");
classReferencesGraph.addEdge("A", "B");
classReferencesGraph.addEdge("B", "C");
classReferencesGraph.addEdge("C", "A");
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
sutCircularReferenceChecker.detectCycles(classReferencesGraph);
assertEquals(3, cyclesForEveryVertexMap.size());
}

@DisplayName("Create graph image in given outputDirectory")
@Test
public void createImageTest() throws IOException {
Graph<String, DefaultEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
Graph<String, DefaultWeightedEdge> classReferencesGraph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class);
classReferencesGraph.addVertex("A");
classReferencesGraph.addVertex("B");
classReferencesGraph.addVertex("C");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import java.io.File;
import java.io.IOException;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -16,16 +16,16 @@ class JavaProjectParserTests {

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

@DisplayName("Given a valid source directory input parameter return a valid graph.")
@Test
public void parseSourceDirectoryTest() throws IOException {
void parseSourceDirectoryTest() throws IOException {
File srcDirectory = new File("src/test/resources/javaSrcDirectory");
Graph<String, DefaultEdge> classReferencesGraph =
Graph<String, DefaultWeightedEdge> classReferencesGraph =
sutJavaProjectParser.getClassReferences(srcDirectory.getAbsolutePath());
assertNotNull(classReferencesGraph);
assertEquals(5, classReferencesGraph.vertexSet().size());
Expand All @@ -42,5 +42,14 @@ public void parseSourceDirectoryTest() throws IOException {
assertTrue(classReferencesGraph.containsEdge("D", "A"));
assertTrue(classReferencesGraph.containsEdge("D", "C"));
assertTrue(classReferencesGraph.containsEdge("E", "D"));

// confirm edge weight calculations
assertEquals(1, getEdgeWeight(classReferencesGraph, "A", "B"));
assertEquals(2, getEdgeWeight(classReferencesGraph, "E", "D"));
}

private static double getEdgeWeight(
Graph<String, DefaultWeightedEdge> classReferencesGraph, String sourceVertex, String targetVertex) {
return classReferencesGraph.getEdgeWeight(classReferencesGraph.getEdge(sourceVertex, targetVertex));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

public class E {
D d;
D d2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pmd.*;
import net.sourceforge.pmd.lang.LanguageRegistry;
Expand All @@ -29,12 +30,12 @@
import org.jgrapht.alg.flow.GusfieldGomoryHuCutTree;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.graph.AsUndirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultWeightedEdge;

@Slf4j
public class CostBenefitCalculator {

private final Map<String, AsSubgraph> renderedSubGraphs = new HashMap<>();
private final Map<String, AsSubgraph<String, DefaultWeightedEdge>> renderedSubGraphs = new HashMap<>();

private Report report;
private String repositoryPath;
Expand All @@ -43,6 +44,9 @@ public class CostBenefitCalculator {
private final ChangePronenessRanker changePronenessRanker;
private final JavaProjectParser javaProjectParser = new JavaProjectParser();

@Getter
private Graph<String, DefaultWeightedEdge> classReferencesGraph;

public CostBenefitCalculator(String repositoryPath) {
this.repositoryPath = repositoryPath;

Expand All @@ -65,15 +69,15 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
List<RankedCycle> rankedCycles = new ArrayList<>();
try {
Map<String, String> classNamesAndPaths = getClassNamesAndPaths();
Graph<String, DefaultEdge> classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
classReferencesGraph = javaProjectParser.getClassReferences(repositoryPath);
CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker();
Map<String, AsSubgraph<String, DefaultEdge>> cyclesForEveryVertexMap =
Map<String, AsSubgraph<String, DefaultWeightedEdge>> cyclesForEveryVertexMap =
circularReferenceChecker.detectCycles(classReferencesGraph);
cyclesForEveryVertexMap.forEach((vertex, subGraph) -> {
int vertexCount = subGraph.vertexSet().size();
int edgeCount = subGraph.edgeSet().size();
double minCut = 0;
Set<DefaultEdge> minCutEdges = null;
Set<DefaultWeightedEdge> minCutEdges;
if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph(subGraph, vertex)) {
if (renderImages) {
try {
Expand All @@ -86,17 +90,11 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re

renderedSubGraphs.put(vertex, subGraph);
log.info("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount);
GusfieldGomoryHuCutTree<String, DefaultEdge> gusfieldGomoryHuCutTree =
GusfieldGomoryHuCutTree<String, DefaultWeightedEdge> gusfieldGomoryHuCutTree =
new GusfieldGomoryHuCutTree<>(new AsUndirectedGraph<>(subGraph));
minCut = gusfieldGomoryHuCutTree.calculateMinCut();
log.info("Min cut weight: " + minCut);
minCutEdges = gusfieldGomoryHuCutTree.getCutEdges();

log.info("Minimum Cut Edges:");
for (DefaultEdge minCutEdge : minCutEdges) {
log.info(minCutEdge.toString());
}

List<CycleNode> cycleNodes = subGraph.vertexSet().stream()
.map(classInCycle -> new CycleNode(classInCycle, classNamesAndPaths.get(classInCycle)))
.collect(Collectors.toList());
Expand All @@ -109,8 +107,8 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
}

for (ScmLogInfo changeRank : changeRanks) {
CycleNode cn = cycleNodeMap.get(changeRank.getPath());
cn.setScmLogInfo(changeRank);
CycleNode cycleNode = cycleNodeMap.get(changeRank.getPath());
cycleNode.setScmLogInfo(changeRank);
}

// sum change proneness ranks
Expand Down Expand Up @@ -148,9 +146,9 @@ public List<RankedCycle> runCycleAnalysis(String outputDirectoryPath, boolean re
return rankedCycles;
}

private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultEdge> subGraph, String vertex) {
private boolean isDuplicateSubGraph(AsSubgraph<String, DefaultWeightedEdge> subGraph, String vertex) {
if (!renderedSubGraphs.isEmpty()) {
for (AsSubgraph renderedSubGraph : renderedSubGraphs.values()) {
for (AsSubgraph<String, DefaultWeightedEdge> renderedSubGraph : renderedSubGraphs.values()) {
if (renderedSubGraph.vertexSet().size() == subGraph.vertexSet().size()
&& renderedSubGraph.edgeSet().size()
== subGraph.edgeSet().size()
Expand Down
Loading