");
+
+ stringBuilder
+ .append("
\n" + "RefactorFirst Report for ")
+ .append(projectName)
+ .append(" ")
+ .append(projectVersion)
+ .append("
\n");
+
+ GitLogReader gitLogReader = new GitLogReader();
+ String projectBaseDir;
+ Optional optionalGitDir;
+
+ if (baseDir != null) {
+ projectBaseDir = baseDir.getPath();
+ optionalGitDir = Optional.ofNullable(gitLogReader.getGitDir(baseDir));
+ } else {
+ projectBaseDir = Paths.get("").toAbsolutePath().toString();
+ optionalGitDir = Optional.ofNullable(gitLogReader.getGitDir(new File(projectBaseDir)));
+ }
+
+ File gitDir;
+ if (optionalGitDir.isPresent()) {
+ gitDir = optionalGitDir.get();
+ } else {
+ log.info(
+ "Done! No Git repository found! Please initialize a Git repository and perform an initial commit.");
+ stringBuilder
+ .append("No Git repository found in project ")
+ .append(projectName)
+ .append(" ")
+ .append(projectVersion)
+ .append(". ");
+ stringBuilder.append("Please initialize a Git repository and perform an initial commit.");
+ stringBuilder.append(THE_END);
+ writeReportToDisk(outputDirectory, filename, stringBuilder);
+ return;
+ }
+
+ String parentOfGitDir = gitDir.getParentFile().getPath();
+ log.info("Project Base Dir: {} ", projectBaseDir);
+ log.info("Parent of Git Dir: {}", parentOfGitDir);
+
+ if (!projectBaseDir.equals(parentOfGitDir)) {
+ log.warn("Project Base Directory does not match Git Parent Directory");
+ stringBuilder.append("Project Base Directory does not match Git Parent Directory. "
+ + "Please refer to the report at the root of the site directory.");
+ stringBuilder.append(THE_END);
+ return;
+ }
+
+ CostBenefitCalculator costBenefitCalculator = new CostBenefitCalculator();
+ List rankedGodClassDisharmonies =
+ costBenefitCalculator.calculateGodClassCostBenefitValues(projectBaseDir);
+
+ List rankedCBODisharmonies =
+ costBenefitCalculator.calculateCBOCostBenefitValues(projectBaseDir);
+
+ if (rankedGodClassDisharmonies.isEmpty() && rankedCBODisharmonies.isEmpty()) {
+ stringBuilder
+ .append("Congratulations! ")
+ .append(projectName)
+ .append(" ")
+ .append(projectVersion)
+ .append(" has no God classes or highly coupled classes!");
+ renderGithubButtons(stringBuilder);
+ log.info("Done! No Disharmonies found!");
+ stringBuilder.append(THE_END);
+ writeReportToDisk(outputDirectory, filename, stringBuilder);
+ return;
+ }
+
+ if (!rankedGodClassDisharmonies.isEmpty() && !rankedCBODisharmonies.isEmpty()) {
+ stringBuilder.append("God Classes");
+ stringBuilder.append("
");
+ stringBuilder.append("Highly Coupled Classes");
+ }
+
+ if (!rankedGodClassDisharmonies.isEmpty()) {
+ rankedGodClassDisharmonies.sort(
+ Comparator.comparing(RankedDisharmony::getRawPriority).reversed());
+
+ int godClassPriority = 1;
+ for (RankedDisharmony rankedGodClassDisharmony : rankedGodClassDisharmonies) {
+ rankedGodClassDisharmony.setPriority(godClassPriority++);
+ }
+
+ stringBuilder.append("");
+
+ writeGodClassGchartJs(rankedGodClassDisharmonies, godClassPriority - 1, outputDirectory);
+ stringBuilder.append("");
+ renderGithubButtons(stringBuilder);
+ stringBuilder.append(GOD_CLASS_CHART_LEGEND);
+
+ stringBuilder.append(
+ "God classes by the numbers: (Refactor Starting with Priority 1)
");
+ stringBuilder.append("");
+
+ // Content
+ stringBuilder.append("");
+ for (String heading : godClassTableHeadings) {
+ stringBuilder.append("| ").append(heading).append(" | ");
+ }
+ stringBuilder.append("
");
+
+ stringBuilder.append("");
+ for (RankedDisharmony rankedGodClassDisharmony : rankedGodClassDisharmonies) {
+ stringBuilder.append("");
+
+ String[] simpleRankedGodClassDisharmonyData = {
+ rankedGodClassDisharmony.getFileName(),
+ rankedGodClassDisharmony.getPriority().toString(),
+ rankedGodClassDisharmony.getChangePronenessRank().toString(),
+ rankedGodClassDisharmony.getEffortRank().toString(),
+ rankedGodClassDisharmony.getWmc().toString(),
+ formatter.format(rankedGodClassDisharmony.getMostRecentCommitTime()),
+ rankedGodClassDisharmony.getCommitCount().toString()
+ };
+
+ String[] detailedRankedGodClassDisharmonyData = {
+ rankedGodClassDisharmony.getFileName(),
+ rankedGodClassDisharmony.getPriority().toString(),
+ rankedGodClassDisharmony.getRawPriority().toString(),
+ rankedGodClassDisharmony.getChangePronenessRank().toString(),
+ rankedGodClassDisharmony.getEffortRank().toString(),
+ rankedGodClassDisharmony.getWmc().toString(),
+ rankedGodClassDisharmony.getWmcRank().toString(),
+ rankedGodClassDisharmony.getAtfd().toString(),
+ rankedGodClassDisharmony.getAtfdRank().toString(),
+ rankedGodClassDisharmony.getTcc().toString(),
+ rankedGodClassDisharmony.getTccRank().toString(),
+ formatter.format(rankedGodClassDisharmony.getFirstCommitTime()),
+ formatter.format(rankedGodClassDisharmony.getMostRecentCommitTime()),
+ rankedGodClassDisharmony.getCommitCount().toString(),
+ rankedGodClassDisharmony.getPath()
+ };
+
+ final String[] rankedDisharmonyData =
+ showDetails ? detailedRankedGodClassDisharmonyData : simpleRankedGodClassDisharmonyData;
+
+ for (String rowData : rankedDisharmonyData) {
+ stringBuilder.append("| ").append(rowData).append(" | ");
+ }
+
+ stringBuilder.append("
");
+ }
+
+ stringBuilder.append("");
+ stringBuilder.append("
");
+ }
+
+ if (!rankedCBODisharmonies.isEmpty()) {
+
+ stringBuilder.append("
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
");
+
+ rankedCBODisharmonies.sort(
+ Comparator.comparing(RankedDisharmony::getRawPriority).reversed());
+
+ int cboPriority = 1;
+ for (RankedDisharmony rankedCBODisharmony : rankedCBODisharmonies) {
+ rankedCBODisharmony.setPriority(cboPriority++);
+ }
+
+ stringBuilder.append(
+ "");
+
+ stringBuilder.append("");
+ writeGCBOGchartJs(rankedCBODisharmonies, cboPriority - 1, outputDirectory);
+ renderGithubButtons(stringBuilder);
+ stringBuilder.append(COUPLING_BETWEEN_OBJECT_CHART_LEGEND);
+
+ stringBuilder.append("Highly Coupled classes by the numbers: (Refactor starting with Priority 1)
");
+ stringBuilder.append("");
+
+ // Content
+ stringBuilder.append("");
+ for (String heading : cboTableHeadings) {
+ stringBuilder.append("| ").append(heading).append(" | ");
+ }
+ stringBuilder.append("
");
+
+ stringBuilder.append("");
+ for (RankedDisharmony rankedCboClassDisharmony : rankedCBODisharmonies) {
+ stringBuilder.append("");
+
+ String[] rankedCboClassDisharmonyData = {
+ rankedCboClassDisharmony.getFileName(),
+ rankedCboClassDisharmony.getPriority().toString(),
+ rankedCboClassDisharmony.getChangePronenessRank().toString(),
+ rankedCboClassDisharmony.getEffortRank().toString(),
+ formatter.format(rankedCboClassDisharmony.getMostRecentCommitTime()),
+ rankedCboClassDisharmony.getCommitCount().toString()
+ };
+
+ for (String rowData : rankedCboClassDisharmonyData) {
+ stringBuilder.append("| ").append(rowData).append(" | ");
+ }
+
+ stringBuilder.append("
");
+ }
+
+ stringBuilder.append("");
+ }
+
+ stringBuilder.append("
");
+ stringBuilder.append(THE_END);
+
+ log.debug(stringBuilder.toString());
+
+ writeReportToDisk(outputDirectory, filename, stringBuilder);
+ log.info("Done! View the report at target/site/{}", filename);
+ }
+
+ void renderGithubButtons(StringBuilder stringBuilder) {
+ stringBuilder.append("
");
+ stringBuilder.append("Show RefactorFirst some ❤️");
+ stringBuilder.append("
");
+ stringBuilder.append(
+ "
Star");
+ stringBuilder.append(
+ "
Fork");
+ stringBuilder.append(
+ "
Watch");
+ stringBuilder.append(
+ "
Issue");
+ stringBuilder.append(
+ "
Sponsor");
+ stringBuilder.append("
");
+ }
+
+ // TODO: Move to another class to allow use by Gradle plugin
+ void writeGodClassGchartJs(
+ List
rankedDisharmonies, int maxPriority, String reportOutputDirectory) {
+ GraphDataGenerator graphDataGenerator = new GraphDataGenerator();
+ String scriptStart = graphDataGenerator.getGodClassScriptStart();
+ String bubbleChartData = graphDataGenerator.generateGodClassBubbleChartData(rankedDisharmonies, maxPriority);
+ String scriptEnd = graphDataGenerator.getGodClassScriptEnd();
+
+ String javascriptCode = scriptStart + bubbleChartData + scriptEnd;
+
+ File reportOutputDir = new File(reportOutputDirectory);
+ if (!reportOutputDir.exists()) {
+ reportOutputDir.mkdirs();
+ }
+ String pathname = reportOutputDirectory + File.separator + "gchart.js";
+
+ File scriptFile = new File(pathname);
+ try {
+ scriptFile.createNewFile();
+ } catch (IOException e) {
+ log.error("Failure creating God Class chart script file", e);
+ }
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(scriptFile))) {
+ writer.write(javascriptCode);
+ } catch (IOException e) {
+ log.error("Error writing chart script file", e);
+ }
+ }
+
+ void writeGCBOGchartJs(List rankedDisharmonies, int maxPriority, String reportOutputDirectory) {
+ GraphDataGenerator graphDataGenerator = new GraphDataGenerator();
+ String scriptStart = graphDataGenerator.getCBOScriptStart();
+ String bubbleChartData = graphDataGenerator.generateCBOBubbleChartData(rankedDisharmonies, maxPriority);
+ String scriptEnd = graphDataGenerator.getCBOScriptEnd();
+
+ String javascriptCode = scriptStart + bubbleChartData + scriptEnd;
+
+ File reportOutputDir = new File(reportOutputDirectory);
+ if (!reportOutputDir.exists()) {
+ reportOutputDir.mkdirs();
+ }
+ String pathname = reportOutputDirectory + File.separator + "gchart2.js";
+
+ File scriptFile = new File(pathname);
+ try {
+ scriptFile.createNewFile();
+ } catch (IOException e) {
+ log.error("Failure creating CBO chart script file", e);
+ }
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(scriptFile))) {
+ writer.write(javascriptCode);
+ } catch (IOException e) {
+ log.error("Error writing CBO chart script file", e);
+ }
+ }
+
+ public String getOutputName() {
+ // This report will generate simple-report.html when invoked in a project with `mvn site`
+ return "refactor-first-report";
+ }
+
+ public String getName(Locale locale) {
+ // Name of the report when listed in the project-reports.html page of a project
+ return "Refactor First Report";
+ }
+
+ public String getDescription(Locale locale) {
+ // Description of the report when listed in the project-reports.html page of a project
+ return "Ranks the disharmonies in a codebase. The classes that should be refactored first "
+ + " have the highest priority values.";
+ }
+}
diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/ReportWriter.java b/report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java
similarity index 73%
rename from refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/ReportWriter.java
rename to report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java
index 69ecf273..bd186fbb 100644
--- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/ReportWriter.java
+++ b/report/src/main/java/org/hjug/refactorfirst/report/ReportWriter.java
@@ -1,4 +1,4 @@
-package org.hjug.mavenreport;
+package org.hjug.refactorfirst.report;
import java.io.BufferedWriter;
import java.io.File;
@@ -6,16 +6,12 @@
import java.nio.charset.Charset;
import java.nio.file.Files;
import lombok.extern.slf4j.Slf4j;
-import org.apache.maven.project.MavenProject;
@Slf4j
public class ReportWriter {
+
public static void writeReportToDisk(
- final MavenProject project, final String filename, final StringBuilder stringBuilder) {
- final String reportOutputDirectory = project.getModel()
- .getReporting()
- .getOutputDirectory()
- .replace("${project.basedir}" + File.separator, "");
+ final String reportOutputDirectory, final String filename, final StringBuilder stringBuilder) {
final File reportOutputDir = new File(reportOutputDirectory);
if (!reportOutputDir.exists()) {
diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/JsonReport.java b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReport.java
similarity index 78%
rename from refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/JsonReport.java
rename to report/src/main/java/org/hjug/refactorfirst/report/json/JsonReport.java
index ab544888..0bfb519b 100644
--- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/JsonReport.java
+++ b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReport.java
@@ -1,4 +1,4 @@
-package org.hjug.mavenreport.RefactorFirstJsonReport;
+package org.hjug.refactorfirst.report.json;
import java.util.List;
import lombok.Builder;
diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/JsonReportDisharmonyEntry.java b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportDisharmonyEntry.java
similarity index 96%
rename from refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/JsonReportDisharmonyEntry.java
rename to report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportDisharmonyEntry.java
index 59ccfbf4..18f17f9f 100644
--- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/JsonReportDisharmonyEntry.java
+++ b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportDisharmonyEntry.java
@@ -1,4 +1,4 @@
-package org.hjug.mavenreport.RefactorFirstJsonReport;
+package org.hjug.refactorfirst.report.json;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
diff --git a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/RefactorFirstMavenJsonReport.java b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java
similarity index 62%
rename from refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/RefactorFirstMavenJsonReport.java
rename to report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java
index 05874365..ea431572 100644
--- a/refactor-first-maven-plugin/src/main/java/org/hjug/mavenreport/RefactorFirstJsonReport/RefactorFirstMavenJsonReport.java
+++ b/report/src/main/java/org/hjug/refactorfirst/report/json/JsonReportExecutor.java
@@ -1,6 +1,6 @@
-package org.hjug.mavenreport.RefactorFirstJsonReport;
+package org.hjug.refactorfirst.report.json;
-import static org.hjug.mavenreport.ReportWriter.writeReportToDisk;
+import static org.hjug.refactorfirst.report.ReportWriter.writeReportToDisk;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -11,36 +11,19 @@
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
-import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugins.annotations.LifecyclePhase;
-import org.apache.maven.plugins.annotations.Mojo;
-import org.apache.maven.plugins.annotations.Parameter;
-import org.apache.maven.plugins.annotations.ResolutionScope;
-import org.apache.maven.project.MavenProject;
import org.hjug.cbc.CostBenefitCalculator;
import org.hjug.cbc.RankedDisharmony;
@Slf4j
-@Mojo(
- name = "jsonreport",
- defaultPhase = LifecyclePhase.SITE,
- requiresDependencyResolution = ResolutionScope.RUNTIME,
- requiresProject = false,
- threadSafe = true,
- inheritByDefault = false)
-public class RefactorFirstMavenJsonReport extends AbstractMojo {
+public class JsonReportExecutor {
+
private static final String FILE_NAME = "refactor-first-data.json";
private static final ObjectMapper MAPPER = new ObjectMapper();
- @Parameter(readonly = true, defaultValue = "${project}")
- private MavenProject project;
-
- @Override
- public void execute() {
+ public void execute(File baseDir, String outputDirectory) {
String projectBaseDir;
- File baseDir = project.getBasedir();
if (baseDir != null) {
projectBaseDir = baseDir.getPath();
} else {
@@ -60,7 +43,7 @@ public void execute() {
try {
final String reportJson = MAPPER.writeValueAsString(report);
- writeReportToDisk(project, FILE_NAME, new StringBuilder(reportJson));
+ writeReportToDisk(outputDirectory, FILE_NAME, new StringBuilder(reportJson));
} catch (final JsonProcessingException jsonProcessingException) {
final String errorMessage = "Could not generate a json report: " + jsonProcessingException;
@@ -69,13 +52,13 @@ public void execute() {
.errors(new ArrayList<>(Collections.singletonList(errorMessage)))
.build();
- writeErrorReport(errorReport);
+ writeErrorReport(errorReport, outputDirectory);
}
}
- private void writeErrorReport(final JsonReport errorReport) {
+ private void writeErrorReport(final JsonReport errorReport, String outputDirectory) {
try {
- writeReportToDisk(project, FILE_NAME, new StringBuilder(MAPPER.writeValueAsString(errorReport)));
+ writeReportToDisk(outputDirectory, FILE_NAME, new StringBuilder(MAPPER.writeValueAsString(errorReport)));
} catch (final JsonProcessingException jsonProcessingException) {
log.error("failed to write error report: ", jsonProcessingException);
}
diff --git a/refactor-first-maven-plugin/src/test/java/org/hjug/mavenreport/RefactorFirstMavenReportTest.java b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java
similarity index 85%
rename from refactor-first-maven-plugin/src/test/java/org/hjug/mavenreport/RefactorFirstMavenReportTest.java
rename to report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java
index 1bfaa1c6..e66080fe 100644
--- a/refactor-first-maven-plugin/src/test/java/org/hjug/mavenreport/RefactorFirstMavenReportTest.java
+++ b/report/src/test/java/org/hjug/refactorfirst/report/HtmlReportTest.java
@@ -1,13 +1,13 @@
-package org.hjug.mavenreport;
+package org.hjug.refactorfirst.report;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Locale;
import org.junit.jupiter.api.Test;
-public class RefactorFirstMavenReportTest {
+public class HtmlReportTest {
- private RefactorFirstMavenReport mavenReport = new RefactorFirstMavenReport();
+ private HtmlReport mavenReport = new HtmlReport();
@Test
void testGetOutputName() {
diff --git a/test-resources/pom.xml b/test-resources/pom.xml
index 2a25abbb..49f0c22c 100644
--- a/test-resources/pom.xml
+++ b/test-resources/pom.xml
@@ -5,7 +5,7 @@
org.hjug.refactorfirst
refactor-first
- 0.4.1-SNAPSHOT
+ 0.5.0-M1-SNAPSHOT
org.hjug.refactorfirst.testresources