16
16
import net .sourceforge .pmd .lang .LanguageRegistry ;
17
17
import org .eclipse .jgit .api .errors .GitAPIException ;
18
18
import org .eclipse .jgit .lib .Repository ;
19
+ import org .hjug .cycledetector .CircularReferenceChecker ;
19
20
import org .hjug .git .ChangePronenessRanker ;
20
21
import org .hjug .git .GitLogReader ;
21
22
import org .hjug .git .ScmLogInfo ;
22
23
import org .hjug .metrics .*;
23
24
import org .hjug .metrics .rules .CBORule ;
25
+ import org .hjug .parser .JavaProjectParser ;
26
+ import org .jgrapht .Graph ;
27
+ import org .jgrapht .alg .flow .GusfieldGomoryHuCutTree ;
28
+ import org .jgrapht .graph .AsSubgraph ;
29
+ import org .jgrapht .graph .AsUndirectedGraph ;
30
+ import org .jgrapht .graph .DefaultEdge ;
24
31
25
32
@ Slf4j
26
33
public class CostBenefitCalculator {
27
34
35
+ private final Map <String , AsSubgraph > renderedSubGraphs = new HashMap <>();
36
+
28
37
private Report report ;
29
38
private String repositoryPath ;
30
39
private final GitLogReader gitLogReader = new GitLogReader ();
31
40
private Repository repository = null ;
32
41
private final ChangePronenessRanker changePronenessRanker ;
42
+ private final JavaProjectParser javaProjectParser = new JavaProjectParser ();
33
43
34
44
public CostBenefitCalculator (String repositoryPath ) {
35
45
this .repositoryPath = repositoryPath ;
@@ -48,6 +58,100 @@ public CostBenefitCalculator(String repositoryPath) {
48
58
changePronenessRanker = new ChangePronenessRanker (repository , gitLogReader );
49
59
}
50
60
61
+ public List <RankedCycle > runCycleAnalysis () {
62
+ List <RankedCycle > rankedCycles = new ArrayList <>();
63
+ try {
64
+ Map <String , String > classNamesAndPaths = getClassNamesAndPaths ();
65
+ Graph <String , DefaultEdge > classReferencesGraph = javaProjectParser .getClassReferences (repositoryPath );
66
+ CircularReferenceChecker circularReferenceChecker = new CircularReferenceChecker ();
67
+ Map <String , AsSubgraph <String , DefaultEdge >> cyclesForEveryVertexMap =
68
+ circularReferenceChecker .detectCycles (classReferencesGraph );
69
+ cyclesForEveryVertexMap .forEach ((vertex , subGraph ) -> {
70
+ int vertexCount = subGraph .vertexSet ().size ();
71
+ int edgeCount = subGraph .edgeSet ().size ();
72
+ double minCut = 0 ;
73
+ Set <DefaultEdge > minCutEdges = null ;
74
+ if (vertexCount > 1 && edgeCount > 1 && !isDuplicateSubGraph (subGraph , vertex )) {
75
+ // circularReferenceChecker.createImage(outputDirectoryPath, subGraph, vertex);
76
+ renderedSubGraphs .put (vertex , subGraph );
77
+ log .info ("Vertex: " + vertex + " vertex count: " + vertexCount + " edge count: " + edgeCount );
78
+ GusfieldGomoryHuCutTree <String , DefaultEdge > gusfieldGomoryHuCutTree =
79
+ new GusfieldGomoryHuCutTree <>(new AsUndirectedGraph <>(subGraph ));
80
+ minCut = gusfieldGomoryHuCutTree .calculateMinCut ();
81
+ log .info ("Min cut weight: " + minCut );
82
+ minCutEdges = gusfieldGomoryHuCutTree .getCutEdges ();
83
+
84
+ log .info ("Minimum Cut Edges:" );
85
+ for (DefaultEdge minCutEdge : minCutEdges ) {
86
+ log .info (minCutEdge .toString ());
87
+ }
88
+ }
89
+
90
+ List <CycleNode > cycleNodes = subGraph .vertexSet ().stream ()
91
+ .map (classInCycle -> new CycleNode (classInCycle , classNamesAndPaths .get (classInCycle )))
92
+ .collect (Collectors .toList ());
93
+ List <ScmLogInfo > changeRanks = getRankedChangeProneness (cycleNodes );
94
+
95
+ Map <String , CycleNode > cycleNodeMap = new HashMap <>();
96
+
97
+ for (CycleNode cycleNode : cycleNodes ) {
98
+ cycleNodeMap .put (cycleNode .getFileName (), cycleNode );
99
+ }
100
+
101
+ for (ScmLogInfo changeRank : changeRanks ) {
102
+ CycleNode cn = cycleNodeMap .get (changeRank .getPath ());
103
+ cn .setScmLogInfo (changeRank );
104
+ }
105
+
106
+ // sum change proneness ranks
107
+ int changePronenessRankSum = changeRanks .stream ()
108
+ .mapToInt (ScmLogInfo ::getChangePronenessRank )
109
+ .sum ();
110
+ rankedCycles .add (new RankedCycle (
111
+ vertex ,
112
+ changePronenessRankSum ,
113
+ subGraph .vertexSet (),
114
+ subGraph .edgeSet (),
115
+ minCut ,
116
+ minCutEdges ,
117
+ cycleNodes ));
118
+ });
119
+
120
+ rankedCycles .sort (Comparator .comparing (RankedCycle ::getAverageChangeProneness ));
121
+ int cpr = 1 ;
122
+ for (RankedCycle rankedCycle : rankedCycles ) {
123
+ rankedCycle .setChangePronenessRank (cpr ++);
124
+ }
125
+
126
+ rankedCycles .sort (Comparator .comparing (RankedCycle ::getRawPriority ).reversed ());
127
+
128
+ int priority = 1 ;
129
+ for (RankedCycle rankedCycle : rankedCycles ) {
130
+ rankedCycle .setPriority (priority ++);
131
+ }
132
+
133
+ } catch (IOException e ) {
134
+ throw new RuntimeException (e );
135
+ }
136
+
137
+ return rankedCycles ;
138
+ }
139
+
140
+ private boolean isDuplicateSubGraph (AsSubgraph <String , DefaultEdge > subGraph , String vertex ) {
141
+ if (!renderedSubGraphs .isEmpty ()) {
142
+ for (AsSubgraph renderedSubGraph : renderedSubGraphs .values ()) {
143
+ if (renderedSubGraph .vertexSet ().size () == subGraph .vertexSet ().size ()
144
+ && renderedSubGraph .edgeSet ().size ()
145
+ == subGraph .edgeSet ().size ()
146
+ && renderedSubGraph .vertexSet ().contains (vertex )) {
147
+ return true ;
148
+ }
149
+ }
150
+ }
151
+
152
+ return false ;
153
+ }
154
+
51
155
// copied from PMD's PmdTaskImpl.java and modified
52
156
public void runPmdAnalysis () throws IOException {
53
157
PMDConfiguration configuration = new PMDConfiguration ();
@@ -186,4 +290,31 @@ private List<CBOClass> getCBOClasses() {
186
290
private String getFileName (RuleViolation violation ) {
187
291
return violation .getFileId ().getUriString ().replace ("file:///" + repositoryPath .replace ("\\ " , "/" ) + "/" , "" );
188
292
}
293
+
294
+ public Map <String , String > getClassNamesAndPaths () throws IOException {
295
+
296
+ Map <String , String > fileNamePaths = new HashMap <>();
297
+
298
+ Files .walk (Paths .get (repositoryPath )).forEach (path -> {
299
+ String filename = path .getFileName ().toString ();
300
+ if (filename .endsWith (".java" )) {
301
+ fileNamePaths .put (
302
+ getClassName (filename ),
303
+ path .toUri ().toString ().replace ("file:///" + repositoryPath .replace ("\\ " , "/" ) + "/" , "" ));
304
+ }
305
+ });
306
+
307
+ return fileNamePaths ;
308
+ }
309
+
310
+ /**
311
+ * Extract class name from java file name
312
+ * Example : MyJavaClass.java becomes MyJavaClass
313
+ *
314
+ * @param javaFileName
315
+ * @return
316
+ */
317
+ private String getClassName (String javaFileName ) {
318
+ return javaFileName .substring (0 , javaFileName .indexOf ('.' ));
319
+ }
189
320
}
0 commit comments