Skip to content

Commit 8d0444f

Browse files
Merge pull request #87 from refactorfirst/Refactor
Refactor codebase to improve analysis performance, HTML report output look, and developer ergonomics
2 parents b34dea1 + bbf4124 commit 8d0444f

File tree

14 files changed

+111
-135
lines changed

14 files changed

+111
-135
lines changed

.github/workflows/maven.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ jobs:
2424
- name: Build With Maven
2525
env:
2626
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
27-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
28-
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
27+
run: mvn -B verify
2928

3029

3130
# Comment "Build With Maven" and uncomment the below when you want a snapshot build to be deployed

change-proneness-ranker/src/main/java/org/hjug/git/ChangePronenessRanker.java

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
11
package org.hjug.git;
22

33
import java.io.IOException;
4-
import java.util.Comparator;
5-
import java.util.List;
6-
import java.util.TreeMap;
4+
import java.util.*;
75
import lombok.extern.slf4j.Slf4j;
86
import org.eclipse.jgit.api.errors.GitAPIException;
97
import org.eclipse.jgit.lib.Repository;
108

119
@Slf4j
1210
public class ChangePronenessRanker {
1311

14-
private Repository repository;
15-
private RepositoryLogReader repositoryLogReader;
16-
17-
public ChangePronenessRanker(Repository repository, RepositoryLogReader repositoryLogReader) {
18-
this.repositoryLogReader = repositoryLogReader;
19-
this.repository = repository;
20-
}
21-
22-
public void rankChangeProneness(List<ScmLogInfo> scmLogInfos) {
23-
TreeMap<Integer, Integer> changeCountsByTimeStamps = new TreeMap<>();
12+
private final TreeMap<Integer, Integer> changeCountsByTimeStamps = new TreeMap<>();
13+
private final Map<String, ScmLogInfo> cachedScmLogInfos = new HashMap<>();
2414

15+
public ChangePronenessRanker(Repository repository, GitLogReader repositoryLogReader) {
2516
try {
2617
log.info("Capturing change count based on commit timestamps");
2718
changeCountsByTimeStamps.putAll(repositoryLogReader.captureChangeCountByCommitTimestamp(repository));
2819
} catch (IOException | GitAPIException e) {
2920
log.error("Error reading from repository: {}", e.getMessage());
3021
}
22+
}
3123

24+
public void rankChangeProneness(List<ScmLogInfo> scmLogInfos) {
3225
for (ScmLogInfo scmLogInfo : scmLogInfos) {
33-
int commitsInRepositorySinceCreation =
34-
changeCountsByTimeStamps.tailMap(scmLogInfo.getEarliestCommit()).values().stream()
35-
.mapToInt(i -> i)
36-
.sum();
37-
38-
scmLogInfo.setChangeProneness((float) scmLogInfo.getCommitCount() / commitsInRepositorySinceCreation);
26+
if (!cachedScmLogInfos.containsKey(scmLogInfo.getPath())) {
27+
int commitsInRepositorySinceCreation =
28+
changeCountsByTimeStamps.tailMap(scmLogInfo.getEarliestCommit()).values().stream()
29+
.mapToInt(i -> i)
30+
.sum();
31+
32+
scmLogInfo.setChangeProneness((float) scmLogInfo.getCommitCount() / commitsInRepositorySinceCreation);
33+
cachedScmLogInfos.put(scmLogInfo.getPath(), scmLogInfo);
34+
} else {
35+
scmLogInfo.setChangeProneness(
36+
cachedScmLogInfos.get(scmLogInfo.getPath()).getChangeProneness());
37+
}
3938
}
4039

4140
scmLogInfos.sort(Comparator.comparing(ScmLogInfo::getChangeProneness));

change-proneness-ranker/src/main/java/org/hjug/git/GitLogReader.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,14 @@
1515
import org.eclipse.jgit.util.io.NullOutputStream;
1616

1717
@Slf4j
18-
public class GitLogReader implements RepositoryLogReader {
18+
public class GitLogReader {
1919

2020
static final String JAVA_FILE_TYPE = ".java";
2121

2222
// Based on
2323
// https://github.com/Cosium/git-code-format-maven-plugin/blob/master/src/main/java/com/cosium/code/format/AbstractMavenGitCodeFormatMojo.java
2424
// MIT License
2525
// Move to a provider?
26-
@Override
2726
public Repository gitRepository(File basedir) throws IOException {
2827
Repository gitRepository;
2928
FileRepositoryBuilder repositoryBuilder = new FileRepositoryBuilder().findGitDir(basedir);
@@ -46,7 +45,6 @@ public File getGitDir(File basedir) {
4645
// https://stackoverflow.com/a/19950970/346247
4746
// and
4847
// https://github.com/centic9/jgit-cookbook/blob/master/src/main/java/org/dstadler/jgit/api/ReadFileFromCommit.java
49-
@Override
5048
public Map<String, ByteArrayOutputStream> listRepositoryContentsAtHEAD(Repository repository) throws IOException {
5149
Ref head = repository.exactRef("HEAD");
5250
// a RevWalk allows us to walk over commits based on some filtering that is defined
@@ -88,7 +86,6 @@ public Map<String, ByteArrayOutputStream> listRepositoryContentsAtHEAD(Repositor
8886
* @return a LogInfo object
8987
* @throws GitAPIException
9088
*/
91-
@Override
9289
public ScmLogInfo fileLog(Repository repository, String path) throws GitAPIException, IOException {
9390
Git git = new Git(repository);
9491
ObjectId branchId = repository.resolve("HEAD");
@@ -118,7 +115,6 @@ public ScmLogInfo fileLog(Repository repository, String path) throws GitAPIExcep
118115
}
119116

120117
// based on https://stackoverflow.com/questions/27361538/how-to-show-changes-between-commits-with-jgit
121-
@Override
122118
public TreeMap<Integer, Integer> captureChangeCountByCommitTimestamp(Repository repository)
123119
throws IOException, GitAPIException {
124120

change-proneness-ranker/src/main/java/org/hjug/git/RepositoryLogReader.java

Lines changed: 0 additions & 21 deletions
This file was deleted.

change-proneness-ranker/src/test/java/org/hjug/git/ChangePronenessRankerTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@
1111
import org.junit.jupiter.api.BeforeEach;
1212
import org.junit.jupiter.api.Test;
1313

14-
public class ChangePronenessRankerTest {
14+
class ChangePronenessRankerTest {
1515

1616
private ChangePronenessRanker changePronenessRanker;
17-
private RepositoryLogReader repositoryLogReader;
17+
private GitLogReader repositoryLogReader;
1818

1919
@BeforeEach
2020
public void setUp() {
21-
repositoryLogReader = mock(RepositoryLogReader.class);
22-
changePronenessRanker = new ChangePronenessRanker(null, repositoryLogReader);
21+
repositoryLogReader = mock(GitLogReader.class);
2322
}
2423

2524
// TODO: this should probably be a cucumber test
@@ -34,6 +33,7 @@ void testChangePronenessCalculation() throws IOException, GitAPIException {
3433

3534
when(repositoryLogReader.captureChangeCountByCommitTimestamp(any())).thenReturn(commitsWithChangeCounts);
3635

36+
changePronenessRanker = new ChangePronenessRanker(null, repositoryLogReader);
3737
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
3838
scmLogInfos.add(scmLogInfo);
3939
changePronenessRanker.rankChangeProneness(scmLogInfos);
@@ -58,6 +58,7 @@ void testRankChangeProneness() throws IOException, GitAPIException {
5858
commitsWithChangeCounts.put(scmLogInfo2.getEarliestCommit() + 10 * 60, 5);
5959

6060
when(repositoryLogReader.captureChangeCountByCommitTimestamp(any())).thenReturn(commitsWithChangeCounts);
61+
changePronenessRanker = new ChangePronenessRanker(null, repositoryLogReader);
6162

6263
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
6364
scmLogInfos.add(scmLogInfo);

cli/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@
6060
<mainClass>org.hjug.refactorfirst.Main</mainClass>
6161
</transformer>
6262
</transformers>
63+
<filters>
64+
<filter>
65+
<artifact>*:*</artifact>
66+
<excludes>
67+
<exclude>META-INF/*.SF</exclude>
68+
<exclude>META-INF/*.DSA</exclude>
69+
<exclude>META-INF/*.RSA</exclude>
70+
</excludes>
71+
</filter>
72+
</filters>
6373
</configuration>
6474
</execution>
6575
</executions>

cli/src/main/java/org/hjug/refactorfirst/ReportCommand.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.apache.maven.project.MavenProject;
1212
import org.hjug.refactorfirst.report.CsvReport;
1313
import org.hjug.refactorfirst.report.HtmlReport;
14+
import org.hjug.refactorfirst.report.SimpleHtmlReport;
1415
import org.hjug.refactorfirst.report.json.JsonReportExecutor;
1516
import picocli.CommandLine.Command;
1617

@@ -58,6 +59,10 @@ public Integer call() {
5859
inferArgumentsFromMavenProject();
5960
populateDefaultArguments();
6061
switch (reportType) {
62+
case SIMPLE_HTML:
63+
SimpleHtmlReport simpleHtmlReport = new SimpleHtmlReport();
64+
simpleHtmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir);
65+
return 0;
6166
case HTML:
6267
HtmlReport htmlReport = new HtmlReport();
6368
htmlReport.execute(showDetails, projectName, projectVersion, outputDirectory, baseDir);

cli/src/main/java/org/hjug/refactorfirst/ReportType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.hjug.refactorfirst;
22

33
public enum ReportType {
4+
SIMPLE_HTML,
45
HTML,
56
JSON,
67
CSV;

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

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import org.eclipse.jgit.lib.Repository;
1919
import org.hjug.git.ChangePronenessRanker;
2020
import org.hjug.git.GitLogReader;
21-
import org.hjug.git.RepositoryLogReader;
2221
import org.hjug.git.ScmLogInfo;
2322
import org.hjug.metrics.*;
2423
import org.hjug.metrics.rules.CBORule;
@@ -27,11 +26,30 @@
2726
public class CostBenefitCalculator {
2827

2928
private Report report;
30-
private String projBaseDir = null;
29+
private String repositoryPath;
30+
private final GitLogReader gitLogReader = new GitLogReader();
31+
private Repository repository = null;
32+
private final ChangePronenessRanker changePronenessRanker;
33+
34+
public CostBenefitCalculator(String repositoryPath) {
35+
this.repositoryPath = repositoryPath;
36+
37+
log.info("Initiating Cost Benefit calculation");
38+
try {
39+
repository = gitLogReader.gitRepository(new File(repositoryPath));
40+
for (String file :
41+
gitLogReader.listRepositoryContentsAtHEAD(repository).keySet()) {
42+
log.info("Files at HEAD: {}", file);
43+
}
44+
} catch (IOException e) {
45+
log.error("Failure to access Git repository", e);
46+
}
47+
48+
changePronenessRanker = new ChangePronenessRanker(repository, gitLogReader);
49+
}
3150

3251
// copied from PMD's PmdTaskImpl.java and modified
33-
public void runPmdAnalysis(String projectBaseDir) throws IOException {
34-
projBaseDir = projectBaseDir;
52+
public void runPmdAnalysis() throws IOException {
3553
PMDConfiguration configuration = new PMDConfiguration();
3654

3755
try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
@@ -42,35 +60,20 @@ public void runPmdAnalysis(String projectBaseDir) throws IOException {
4260
cboClassRule.setLanguage(LanguageRegistry.PMD.getLanguageByFullName("Java"));
4361
pmd.addRuleSet(RuleSet.forSingleRule(cboClassRule));
4462

45-
log.info("files to be scanned: " + Paths.get(projectBaseDir));
63+
log.info("files to be scanned: " + Paths.get(repositoryPath));
4664

47-
try (Stream<Path> files = Files.walk(Paths.get(projectBaseDir))) {
65+
try (Stream<Path> files = Files.walk(Paths.get(repositoryPath))) {
4866
files.forEach(file -> pmd.files().addFile(file));
4967
}
5068

5169
report = pmd.performAnalysisAndCollectReport();
5270
}
5371
}
5472

55-
public List<RankedDisharmony> calculateGodClassCostBenefitValues(String repositoryPath) {
56-
57-
RepositoryLogReader repositoryLogReader = new GitLogReader();
58-
Repository repository = null;
59-
log.info("Initiating Cost Benefit calculation");
60-
try {
61-
repository = repositoryLogReader.gitRepository(new File(repositoryPath));
62-
for (String file :
63-
repositoryLogReader.listRepositoryContentsAtHEAD(repository).keySet()) {
64-
log.info("Files at HEAD: {}", file);
65-
}
66-
} catch (IOException e) {
67-
log.error("Failure to access Git repository", e);
68-
}
69-
70-
// pass repo path here, not ByteArrayOutputStream
73+
public List<RankedDisharmony> calculateGodClassCostBenefitValues() {
7174
List<GodClass> godClasses = getGodClasses();
7275

73-
List<ScmLogInfo> scmLogInfos = getRankedChangeProneness(repositoryLogReader, repository, godClasses);
76+
List<ScmLogInfo> scmLogInfos = getRankedChangeProneness(godClasses);
7477

7578
Map<String, ScmLogInfo> rankedLogInfosByPath =
7679
scmLogInfos.stream().collect(Collectors.toMap(ScmLogInfo::getPath, logInfo -> logInfo, (a, b) -> b));
@@ -114,15 +117,14 @@ private List<GodClass> getGodClasses() {
114117
return godClasses;
115118
}
116119

117-
<T extends Disharmony> List<ScmLogInfo> getRankedChangeProneness(
118-
RepositoryLogReader repositoryLogReader, Repository repository, List<T> disharmonies) {
120+
<T extends Disharmony> List<ScmLogInfo> getRankedChangeProneness(List<T> disharmonies) {
119121
List<ScmLogInfo> scmLogInfos = new ArrayList<>();
120122
log.info("Calculating Change Proneness");
121123
for (Disharmony disharmony : disharmonies) {
122124
String path = disharmony.getFileName();
123125
ScmLogInfo scmLogInfo = null;
124126
try {
125-
scmLogInfo = repositoryLogReader.fileLog(repository, path);
127+
scmLogInfo = gitLogReader.fileLog(repository, path);
126128
log.info("Successfully fetched scmLogInfo for {}", scmLogInfo.getPath());
127129
} catch (GitAPIException | IOException e) {
128130
log.error("Error reading Git repository contents.", e);
@@ -136,25 +138,14 @@ <T extends Disharmony> List<ScmLogInfo> getRankedChangeProneness(
136138
}
137139
}
138140

139-
ChangePronenessRanker changePronenessRanker = new ChangePronenessRanker(repository, repositoryLogReader);
140141
changePronenessRanker.rankChangeProneness(scmLogInfos);
141142
return scmLogInfos;
142143
}
143144

144-
public List<RankedDisharmony> calculateCBOCostBenefitValues(String repositoryPath) {
145-
146-
RepositoryLogReader repositoryLogReader = new GitLogReader();
147-
Repository repository = null;
148-
log.info("Initiating Cost Benefit calculation");
149-
try {
150-
repository = repositoryLogReader.gitRepository(new File(repositoryPath));
151-
} catch (IOException e) {
152-
log.error("Failure to access Git repository", e);
153-
}
154-
145+
public List<RankedDisharmony> calculateCBOCostBenefitValues() {
155146
List<CBOClass> cboClasses = getCBOClasses();
156147

157-
List<ScmLogInfo> scmLogInfos = getRankedChangeProneness(repositoryLogReader, repository, cboClasses);
148+
List<ScmLogInfo> scmLogInfos = getRankedChangeProneness(cboClasses);
158149

159150
Map<String, ScmLogInfo> rankedLogInfosByPath =
160151
scmLogInfos.stream().collect(Collectors.toMap(ScmLogInfo::getPath, logInfo -> logInfo, (a, b) -> b));
@@ -193,6 +184,6 @@ private List<CBOClass> getCBOClasses() {
193184
}
194185

195186
private String getFileName(RuleViolation violation) {
196-
return violation.getFileId().getUriString().replace("file:///" + projBaseDir.replace("\\", "/") + "/", "");
187+
return violation.getFileId().getUriString().replace("file:///" + repositoryPath.replace("\\", "/") + "/", "");
197188
}
198189
}

cost-benefit-calculator/src/test/java/org/hjug/cbc/CostBenefitCalculatorTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ void testCBOViolation() throws IOException, GitAPIException, InterruptedExceptio
4747
git.add().addFilepattern(".").call();
4848
RevCommit firstCommit = git.commit().setMessage("message").call();
4949

50-
CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator();
51-
costBenefitCalculator.runPmdAnalysis(git.getRepository().getDirectory().getParent());
52-
List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateCBOCostBenefitValues(
53-
git.getRepository().getDirectory().getPath());
50+
CostBenefitCalculator costBenefitCalculator =
51+
new CostBenefitCalculator(git.getRepository().getDirectory().getParent());
52+
costBenefitCalculator.runPmdAnalysis();
53+
List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateCBOCostBenefitValues();
5454

5555
Assertions.assertFalse(disharmonies.isEmpty());
5656
}
@@ -80,10 +80,10 @@ void testCostBenefitCalculation() throws IOException, GitAPIException, Interrupt
8080
git.add().addFilepattern(".").call();
8181
RevCommit secondCommit = git.commit().setMessage("message").call();
8282

83-
CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator();
84-
costBenefitCalculator.runPmdAnalysis(git.getRepository().getDirectory().getParent());
85-
List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues(
86-
git.getRepository().getDirectory().getPath());
83+
CostBenefitCalculator costBenefitCalculator =
84+
new CostBenefitCalculator(git.getRepository().getDirectory().getParent());
85+
costBenefitCalculator.runPmdAnalysis();
86+
List<RankedDisharmony> disharmonies = costBenefitCalculator.calculateGodClassCostBenefitValues();
8787

8888
Assertions.assertEquals(1, disharmonies.get(0).getRawPriority().intValue());
8989
Assertions.assertEquals(1, disharmonies.get(1).getRawPriority().intValue());

0 commit comments

Comments
 (0)