1616import net .sourceforge .pmd .lang .LanguageRegistry ;
1717import org .eclipse .jgit .api .errors .GitAPIException ;
1818import org .eclipse .jgit .lib .Repository ;
19+ import org .hjug .cycledetector .CircularReferenceChecker ;
1920import org .hjug .git .ChangePronenessRanker ;
2021import org .hjug .git .GitLogReader ;
2122import org .hjug .git .ScmLogInfo ;
2223import org .hjug .metrics .*;
2324import 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 ;
2431
2532@ Slf4j
2633public class CostBenefitCalculator {
2734
35+ private final Map <String , AsSubgraph > renderedSubGraphs = new HashMap <>();
36+
2837 private Report report ;
2938 private String repositoryPath ;
3039 private final GitLogReader gitLogReader = new GitLogReader ();
3140 private Repository repository = null ;
3241 private final ChangePronenessRanker changePronenessRanker ;
42+ private final JavaProjectParser javaProjectParser = new JavaProjectParser ();
3343
3444 public CostBenefitCalculator (String repositoryPath ) {
3545 this .repositoryPath = repositoryPath ;
@@ -48,6 +58,100 @@ public CostBenefitCalculator(String repositoryPath) {
4858 changePronenessRanker = new ChangePronenessRanker (repository , gitLogReader );
4959 }
5060
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+
51155 // copied from PMD's PmdTaskImpl.java and modified
52156 public void runPmdAnalysis () throws IOException {
53157 PMDConfiguration configuration = new PMDConfiguration ();
@@ -186,4 +290,31 @@ private List<CBOClass> getCBOClasses() {
186290 private String getFileName (RuleViolation violation ) {
187291 return violation .getFileId ().getUriString ().replace ("file:///" + repositoryPath .replace ("\\ " , "/" ) + "/" , "" );
188292 }
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+ }
189320}
0 commit comments