11package org .hjug .cbc ;
22
3- import java .io .ByteArrayInputStream ;
4- import java .io .ByteArrayOutputStream ;
3+ import static net .sourceforge .pmd .RuleViolation .CLASS_NAME ;
4+ import static net .sourceforge .pmd .RuleViolation .PACKAGE_NAME ;
5+
56import java .io .File ;
67import java .io .IOException ;
8+ import java .nio .file .Files ;
9+ import java .nio .file .Path ;
10+ import java .nio .file .Paths ;
711import java .util .*;
812import java .util .stream .Collectors ;
13+ import java .util .stream .Stream ;
914import lombok .extern .slf4j .Slf4j ;
15+ import net .sourceforge .pmd .*;
16+ import net .sourceforge .pmd .lang .LanguageRegistry ;
1017import org .eclipse .jgit .api .errors .GitAPIException ;
1118import org .eclipse .jgit .lib .Repository ;
1219import org .hjug .git .ChangePronenessRanker ;
1320import org .hjug .git .GitLogReader ;
1421import org .hjug .git .RepositoryLogReader ;
1522import org .hjug .git .ScmLogInfo ;
1623import org .hjug .metrics .*;
24+ import org .hjug .metrics .rules .CBORule ;
1725
1826@ Slf4j
1927public class CostBenefitCalculator {
2028
21- Map <String , ByteArrayOutputStream > filesToScan = new HashMap <>();
29+ private Report report ;
30+ private String projBaseDir = null ;
31+
32+ // copied from PMD's PmdTaskImpl.java and modified
33+ public void runPmdAnalysis (String projectBaseDir ) throws IOException {
34+ projBaseDir = projectBaseDir ;
35+ PMDConfiguration configuration = new PMDConfiguration ();
36+
37+ try (PmdAnalysis pmd = PmdAnalysis .create (configuration )) {
38+ RuleSetLoader rulesetLoader = pmd .newRuleSetLoader ();
39+ pmd .addRuleSets (rulesetLoader .loadRuleSetsWithoutException (List .of ("category/java/design.xml" )));
40+
41+ Rule cboClassRule = new CBORule ();
42+ cboClassRule .setLanguage (LanguageRegistry .PMD .getLanguageByFullName ("Java" ));
43+ pmd .addRuleSet (RuleSet .forSingleRule (cboClassRule ));
44+
45+ log .info ("files to be scanned: " + Paths .get (projectBaseDir ));
46+
47+ try (Stream <Path > files = Files .walk (Paths .get (projectBaseDir ))) {
48+ files .forEach (file -> pmd .files ().addFile (file ));
49+ }
50+
51+ report = pmd .performAnalysisAndCollectReport ();
52+ }
53+ }
2254
2355 public List <RankedDisharmony > calculateGodClassCostBenefitValues (String repositoryPath ) {
2456
@@ -27,11 +59,16 @@ public List<RankedDisharmony> calculateGodClassCostBenefitValues(String reposito
2759 log .info ("Initiating Cost Benefit calculation" );
2860 try {
2961 repository = repositoryLogReader .gitRepository (new File (repositoryPath ));
62+ for (String file :
63+ repositoryLogReader .listRepositoryContentsAtHEAD (repository ).keySet ()) {
64+ log .info ("Files at HEAD: {}" , file );
65+ }
3066 } catch (IOException e ) {
3167 log .error ("Failure to access Git repository" , e );
3268 }
3369
34- List <GodClass > godClasses = getGodClasses (getFilesToScan (repositoryLogReader , repository ));
70+ // pass repo path here, not ByteArrayOutputStream
71+ List <GodClass > godClasses = getGodClasses ();
3572
3673 List <ScmLogInfo > scmLogInfos = getRankedChangeProneness (repositoryLogReader , repository , godClasses );
3774
@@ -40,52 +77,70 @@ public List<RankedDisharmony> calculateGodClassCostBenefitValues(String reposito
4077
4178 List <RankedDisharmony > rankedDisharmonies = new ArrayList <>();
4279 for (GodClass godClass : godClasses ) {
43- rankedDisharmonies .add (new RankedDisharmony (godClass , rankedLogInfosByPath .get (godClass .getFileName ())));
80+ if (rankedLogInfosByPath .containsKey (godClass .getFileName ())) {
81+ rankedDisharmonies .add (
82+ new RankedDisharmony (godClass , rankedLogInfosByPath .get (godClass .getFileName ())));
83+ }
84+ }
85+
86+ rankedDisharmonies .sort (
87+ Comparator .comparing (RankedDisharmony ::getRawPriority ).reversed ());
88+
89+ int godClassPriority = 1 ;
90+ for (RankedDisharmony rankedGodClassDisharmony : rankedDisharmonies ) {
91+ rankedGodClassDisharmony .setPriority (godClassPriority ++);
4492 }
4593
4694 return rankedDisharmonies ;
4795 }
4896
97+ private List <GodClass > getGodClasses () {
98+ List <GodClass > godClasses = new ArrayList <>();
99+ for (RuleViolation violation : report .getViolations ()) {
100+ if (violation .getRule ().getName ().contains ("GodClass" )) {
101+ GodClass godClass = new GodClass (
102+ violation .getAdditionalInfo ().get (CLASS_NAME ),
103+ getFileName (violation ),
104+ violation .getAdditionalInfo ().get (PACKAGE_NAME ),
105+ violation .getDescription ());
106+ log .info ("God Class identified: {}" , godClass .getFileName ());
107+ godClasses .add (godClass );
108+ }
109+ }
110+
111+ GodClassRanker godClassRanker = new GodClassRanker ();
112+ godClassRanker .rankGodClasses (godClasses );
113+
114+ return godClasses ;
115+ }
116+
49117 <T extends Disharmony > List <ScmLogInfo > getRankedChangeProneness (
50118 RepositoryLogReader repositoryLogReader , Repository repository , List <T > disharmonies ) {
51119 List <ScmLogInfo > scmLogInfos = new ArrayList <>();
52- log .info ("Calculating Change Proneness for each God Class " );
120+ log .info ("Calculating Change Proneness" );
53121 for (Disharmony disharmony : disharmonies ) {
54122 String path = disharmony .getFileName ();
55123 ScmLogInfo scmLogInfo = null ;
56124 try {
57125 scmLogInfo = repositoryLogReader .fileLog (repository , path );
126+ log .info ("Successfully fetched scmLogInfo for {}" , scmLogInfo .getPath ());
58127 } catch (GitAPIException | IOException e ) {
59- log .error ("Error reading Git repository contents" , e );
128+ log .error ("Error reading Git repository contents." , e );
129+ } catch (NullPointerException e ) {
130+ log .error ("Encountered nested class in a class containing a violation. Class: {}" , path );
60131 }
61132
62- scmLogInfos .add (scmLogInfo );
133+ if (null != scmLogInfo ) {
134+ log .info ("adding {}" , scmLogInfo .getPath ());
135+ scmLogInfos .add (scmLogInfo );
136+ }
63137 }
64138
65139 ChangePronenessRanker changePronenessRanker = new ChangePronenessRanker (repository , repositoryLogReader );
66140 changePronenessRanker .rankChangeProneness (scmLogInfos );
67141 return scmLogInfos ;
68142 }
69143
70- private List <GodClass > getGodClasses (Map <String , ByteArrayOutputStream > filesToScan ) {
71- PMDGodClassRuleRunner ruleRunner = new PMDGodClassRuleRunner ();
72-
73- log .info ("Identifying God Classes from files in repository" );
74- List <GodClass > godClasses = new ArrayList <>();
75- for (Map .Entry <String , ByteArrayOutputStream > entry : filesToScan .entrySet ()) {
76- String filePath = entry .getKey ();
77- ByteArrayOutputStream value = entry .getValue ();
78-
79- ByteArrayInputStream inputStream = new ByteArrayInputStream (value .toByteArray ());
80- Optional <GodClass > godClassOptional = ruleRunner .runGodClassRule (filePath , inputStream );
81- godClassOptional .ifPresent (godClasses ::add );
82- }
83-
84- GodClassRanker godClassRanker = new GodClassRanker ();
85- godClassRanker .rankGodClasses (godClasses );
86- return godClasses ;
87- }
88-
89144 public List <RankedDisharmony > calculateCBOCostBenefitValues (String repositoryPath ) {
90145
91146 RepositoryLogReader repositoryLogReader = new GitLogReader ();
@@ -97,7 +152,7 @@ public List<RankedDisharmony> calculateCBOCostBenefitValues(String repositoryPat
97152 log .error ("Failure to access Git repository" , e );
98153 }
99154
100- List <CBOClass > cboClasses = getCBOClasses (getFilesToScan ( repositoryLogReader , repository ) );
155+ List <CBOClass > cboClasses = getCBOClasses ();
101156
102157 List <ScmLogInfo > scmLogInfos = getRankedChangeProneness (repositoryLogReader , repository , cboClasses );
103158
@@ -109,37 +164,35 @@ public List<RankedDisharmony> calculateCBOCostBenefitValues(String repositoryPat
109164 rankedDisharmonies .add (new RankedDisharmony (cboClass , rankedLogInfosByPath .get (cboClass .getFileName ())));
110165 }
111166
112- return rankedDisharmonies ;
113- }
167+ rankedDisharmonies . sort (
168+ Comparator . comparing ( RankedDisharmony :: getRawPriority ). reversed ());
114169
115- private List <CBOClass > getCBOClasses (Map <String , ByteArrayOutputStream > filesToScan ) {
170+ int cboPriority = 1 ;
171+ for (RankedDisharmony rankedCBODisharmony : rankedDisharmonies ) {
172+ rankedCBODisharmony .setPriority (cboPriority ++);
173+ }
116174
117- CBORuleRunner ruleRunner = new CBORuleRunner ();
175+ return rankedDisharmonies ;
176+ }
118177
119- log . info ( "Identifying highly coupled classes from files in repository" );
178+ private List < CBOClass > getCBOClasses () {
120179 List <CBOClass > cboClasses = new ArrayList <>();
121- for (Map .Entry <String , ByteArrayOutputStream > entry : filesToScan .entrySet ()) {
122- String filePath = entry .getKey ();
123- ByteArrayOutputStream value = entry .getValue ();
124-
125- ByteArrayInputStream inputStream = new ByteArrayInputStream (value .toByteArray ());
126- Optional <CBOClass > godClassOptional = ruleRunner .runCBOClassRule (filePath , inputStream );
127- godClassOptional .ifPresent (cboClasses ::add );
180+ for (RuleViolation violation : report .getViolations ()) {
181+ if (violation .getRule ().getName ().contains ("CBORule" )) {
182+ log .info (violation .getDescription ());
183+ CBOClass godClass = new CBOClass (
184+ violation .getAdditionalInfo ().get (CLASS_NAME ),
185+ getFileName (violation ),
186+ violation .getAdditionalInfo ().get (PACKAGE_NAME ),
187+ violation .getDescription ());
188+ log .info ("Highly Coupled class identified: {}" , godClass .getFileName ());
189+ cboClasses .add (godClass );
190+ }
128191 }
129-
130192 return cboClasses ;
131193 }
132194
133- private Map <String , ByteArrayOutputStream > getFilesToScan (
134- RepositoryLogReader repositoryLogReader , Repository repository ) {
135-
136- try {
137- if (filesToScan .isEmpty ()) {
138- filesToScan = repositoryLogReader .listRepositoryContentsAtHEAD (repository );
139- }
140- } catch (IOException e ) {
141- log .error ("Error reading Git repository contents" , e );
142- }
143- return filesToScan ;
195+ private String getFileName (RuleViolation violation ) {
196+ return violation .getFileId ().getUriString ().replace ("file:///" + projBaseDir .replace ("\\ " , "/" ) + "/" , "" );
144197 }
145198}
0 commit comments