apiChanges = jApiCmpAnalyze.useJApiCmp();
- changes.changes().forEach(change -> {
- ExplanationTemplate explanationTemplate = new CompilationErrorTemplate(changes, change);
- explanationTemplate.generateTemplate();
- }
- );
+ CombineResults combineResults = new CombineResults(apiChanges);
+ combineResults.setDependencyGroupID(breakingUpdate.updatedDependency().dependencyGroupID());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ combineResults.setProject("projects/%s".formatted(breakingUpdate.breakingCommit()));
- } catch (Exception e) {
- System.out.println(e.getMessage());
+ combineResults.setMavenLog(new MavenLogAnalyzer(
+ new File("projects/%s/%s/%s.log".formatted(breakingUpdate.breakingCommit(), breakingUpdate.project(), breakingUpdate.breakingCommit()))));
+
+ try {
+ Changes changes = combineResults.analyze();
+
+ System.out.println("Project: " + breakingUpdate.project());
+ System.out.println("Breaking Commit: " + breakingUpdate.breakingCommit());
+ System.out.println("Changes: " + changes.changes().size());
+ explanationStatistics.add(new ExplanationStatistics(breakingUpdate.project(), breakingUpdate.breakingCommit(), changes.changes().size()));
+ ExplanationTemplate explanationTemplate = new CompilationErrorTemplate(changes, "Explanations/" + breakingUpdate.breakingCommit() + ".md");
+ explanationTemplate.generateTemplate();
+ System.out.println("**********************************************************");
+ System.out.println();
+ } catch (IOException e) {
+ System.out.println("Error analyzing breaking update " + breakingUpdate.breakingCommit());
+ System.out.println(e);
+ throw new RuntimeException(e);
+ }
+ try {
+ Path file = Path.of("explanationStatistics.json");
+ Files.deleteIfExists(file);
+ Files.createFile(file);
+ JsonUtils.writeToFile(file, explanationStatistics);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
+
+ } catch (Exception e) {
+ System.out.println("Error processing breaking update " + breakingUpdate.breakingCommit());
+ System.out.println(e);
}
}
+
+ public record ExplanationStatistics(String project, String commit, int changes) {
+ }
}
diff --git a/src/main/java/se/kth/explaining/CompilationErrorTemplate.java b/src/main/java/se/kth/explaining/CompilationErrorTemplate.java
index ba11f00..7a788a4 100644
--- a/src/main/java/se/kth/explaining/CompilationErrorTemplate.java
+++ b/src/main/java/se/kth/explaining/CompilationErrorTemplate.java
@@ -1,41 +1,168 @@
package se.kth.explaining;
+import japicmp.model.JApiMethod;
import se.kth.core.BreakingChange;
import se.kth.core.Changes;
+import se.kth.spoon_compare.SpoonResults;
+
+import static java.util.stream.Collectors.joining;
public class CompilationErrorTemplate extends ExplanationTemplate {
- public CompilationErrorTemplate(Changes changes, BreakingChange breakingChange) {
- super(changes, breakingChange);
+ public CompilationErrorTemplate(Changes changes, String fileName) {
+ super(changes, fileName);
}
@Override
public String getHead() {
+ BreakingChange breakingChange = !changes.changes().isEmpty() ?changes.changes().iterator().next() : null;
+
+
return "CI detected that the dependency upgrade from version **%s** to **%s** has failed. Here are details to help you understand and fix the problem:"
.formatted(breakingChange.getApiChanges().getOldVersion().getName(), breakingChange.getApiChanges().getNewVersion().getName());
}
- @Override
- public String clientError() {
- return ("3. An error was detected in line %s which is making use of an outdated API.\n " +
- "``` java\n %s %s;\n ```").formatted(breakingChange.getErrorInfo().getErrorInfo().getClientLinePosition(),
- breakingChange.getErrorInfo().getErrorInfo().getClientLinePosition(), breakingChange.getErrorInfo().getClientLine());
-
+ public String clientErrorLine(SpoonResults spoonResults) {
+ return " * An error was detected in line %s which is making use of an outdated API.\n".formatted(spoonResults.getErrorInfo().getClientLinePosition()) +
+ " ``` java\n" +
+ " %s %s;\n".formatted(spoonResults.getErrorInfo().getClientLinePosition(), spoonResults.getClientLine()) +
+ " ```\n";
}
@Override
public String logLine() {
- return ("2. The failure is identified from the logs generated in the build process\n " +
- "\n" +
- ">[%s](%s)").formatted(breakingChange.getErrorInfo().getErrorInfo().getErrorMessage(), breakingChange.getErrorInfo().getErrorInfo().getErrorLogGithubLink());
+ return "";
+ }
+
+
+ public String logLineErrorMessage(SpoonResults spoonResults) {
+ return " * >[%s](%s)\n".formatted(spoonResults.getErrorInfo().getErrorMessage(), spoonResults.getErrorInfo().getErrorLogGithubLink());
+
}
+ public String errorSection(BreakingChange breakingChange, int instructions) {
+ StringBuilder message = new StringBuilder();
+ for (SpoonResults spoonResults : breakingChange.getErrorInfo()) {
+ message.append(logLineErrorMessage(spoonResults)).append(clientErrorLine(spoonResults));
+ }
+
+ return message.toString();
+ }
+
+
+ /**
+ * This method generates the broken element section of the markdown file
+ * @return
+ */
@Override
- public String type() {
- return "1. Your client utilizes the instruction **%s** which has been modified in the new version of the dependency."
- .formatted(breakingChange.getApiChanges().getOldElement());
+ public String brokenElement() {
+
+ String message = "";
+ // if there are more than one changes
+ if (!changes.changes().isEmpty()) {
+ String instructions = changes.changes().size() > 1 ? "instructions" : "instruction";
+ String firstLine = "1. Your client utilizes **%d** %s which has been modified in the new version of the dependency."
+ .formatted(changes.changes().size(), instructions);
+
+ String text = "";
+ for (BreakingChange changes : changes.changes()) {
+ String category = translateCategory(changes.getApiChanges().getChangeType().toString());
+ final var singleChange = generateElementExplanation(changes, category, this.changes.changes().size());
+ text = text.concat(singleChange);
+ }
+ message = firstLine + "\n" + text;
+ }
+ return message;
+ }
+
+ private String generateElementExplanation(BreakingChange changes, String category, int instructions) {
+ if (instructions > 1) {
+ return " * \n" +
+ " %s %s which has been %s in the new version of the dependency
\n".formatted(changes.getApiChanges().getInstruction(), changes.getErrorInfo().get(0).getElement(), category) +
+ " \n" +
+ " * \n" +
+ " The failure is identified from the logs generated in the build process.
\n" +
+ " \n" +
+ errorSection(changes, instructions) +
+ "\n" +
+ " \n" +
+ " \n" +
+ newCandidates(changes) +
+ " \n";
+ } else {
+ return " * %s %s which has been %s in the new version of the dependency\n".formatted(changes.getApiChanges().getInstruction(), changes.getErrorInfo().get(0).getElement(), category) +
+ " \n" +
+ " * The failure is identified from the logs generated in the build process. \n" +
+ " \n" +
+ errorSection(changes, instructions) +
+ " \n" +
+ newCandidates(changes);
+
+ }
+ }
+
+
+ /**
+ * This method translates the category of the change to a human-readable format
+ *
+ * To resolve this issue, there are alternative options available in the new version of the dependency that can replace the incompatible instruction currently used in the client. You can consider substituting the existing instruction with one of the following options provided by the new version of the dependency
+ * ``` java
+ * net.datafaker.DateAndTime.between(java.sql.Timestamp,java.sql.Timestamp);
+ *
```
+ *
+ * @param breakingChange BreakingChange
+ * @return Number of new candidates and their method signature
+ */
+ public String newCandidates(BreakingChange breakingChange) {
+ if (breakingChange.getApiChanges().getNewVariants().isEmpty()) {
+ return "";
+ }
+ int amountVariants = breakingChange.getApiChanges().getNewVariants().size();
+ StringBuilder message = new StringBuilder();
+ if (amountVariants > 1) {
+ message.append(" To address this incompatibility, there are ")
+ .append(amountVariants)
+ .append(" alternative options available in the new version of the dependency that can replace the incompatible %s currently used in the client. You can consider substituting the existing %s with one of the following options provided by the new version of the dependency:\n".formatted(breakingChange.getApiChanges().getInstruction().toLowerCase(), breakingChange.getApiChanges().getInstruction().toLowerCase()));
+
+ breakingChange.getApiChanges().getNewVariants().forEach(apiChange -> {
+ JApiMethod method = ((JApiMethod) apiChange.getBehavior());
+ message.append(" ``` java\n")
+ .append(" ").append(methodName(method)).append(";\n")
+ .append(" ```\n")
+ ;
+ });
+ } else {
+ message.append(
+ " To resolve this issue, there are alternative options available in the new version of the dependency that can replace the incompatible %s currently used in the client. You can consider substituting the existing %s with one of the following options provided by the new version of the dependency\n".formatted(breakingChange.getApiChanges().getInstruction().toLowerCase(), breakingChange.getApiChanges().getInstruction().toLowerCase()));
+ breakingChange.getApiChanges().getNewVariants().forEach(apiChange -> {
+ JApiMethod method = ((JApiMethod) apiChange.getBehavior());
+ message.append(" ``` java\n")
+ .append(" ").append(methodName(method)).append(";\n")
+ .append(" ```\n");
+ });
+ }
+ return message.toString();
+ }
+
+ /**
+ * This method returns the method name in the format of returnType methodName(params)
+ *
+ * @param method JApiMethod
+ * @return String
+ */
+ private String methodName(JApiMethod method) {
+
+ String[] fullReturnTypeName = method.getReturnType().getNewReturnType().split("\\.");
+ String returnTypeClass = fullReturnTypeName[fullReturnTypeName.length - 1].equals("n.a.") ? "void" : fullReturnTypeName[fullReturnTypeName.length - 1];
+
+ String params = method.getParameters().stream().map(jApiParameter -> {
+ String[] fullParameterTypeName = jApiParameter.getType().split("\\.");
+ return fullParameterTypeName[fullParameterTypeName.length - 1];
+ }).collect(joining(","));
+
+ return "%s %s(%s)".formatted(returnTypeClass, method.getName(), params);
}
diff --git a/src/main/java/se/kth/explaining/ExplanationTemplate.java b/src/main/java/se/kth/explaining/ExplanationTemplate.java
index 5920d65..e162635 100644
--- a/src/main/java/se/kth/explaining/ExplanationTemplate.java
+++ b/src/main/java/se/kth/explaining/ExplanationTemplate.java
@@ -1,45 +1,59 @@
package se.kth.explaining;
-import se.kth.core.BreakingChange;
import se.kth.core.Changes;
import java.io.FileWriter;
import java.io.IOException;
-import java.util.Date;
+
+import static java.lang.Thread.sleep;
public abstract class ExplanationTemplate {
protected Changes changes;
- BreakingChange breakingChange;
- public ExplanationTemplate(Changes changes, BreakingChange breakingChange) {
+ protected String fileName;
+
+ public ExplanationTemplate(Changes changes, String fileName) {
this.changes = changes;
- this.breakingChange = breakingChange;
+ this.fileName = fileName;
}
public abstract String getHead();
- public abstract String clientError();
-
public abstract String logLine();
- public abstract String type();
+ public abstract String brokenElement();
+
+ public String translateCategory(String category) {
+ return switch (category) {
+ case "[METHOD_REMOVED]":
+ case "REMOVED":
+ yield "removed";
+ case "[METHOD_ADDED]":
+ yield "added";
+ default:
+ yield "";
+ };
+ }
public void generateTemplate() {
+
+ if(changes.changes().isEmpty()){
+ return;
+ }
+
FileWriter markdownFile = null;
try {
- markdownFile = new FileWriter("Explanation_" + new Date().getTime() + ".md");
+ markdownFile = new FileWriter(fileName);
markdownFile.write(getHead());
markdownFile.write("\n");
- markdownFile.write(type());
- markdownFile.write("\n");
- markdownFile.write(logLine());
+ markdownFile.write(brokenElement());
markdownFile.write("\n");
- markdownFile.write(clientError());
markdownFile.write("\n");
markdownFile.close(); //
- } catch (IOException e) {
+ sleep(3000);
+ } catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
diff --git a/src/main/java/se/kth/spoon_compare/Main.java b/src/main/java/se/kth/spoon_compare/Main.java
index 9b3a8af..80f9a6e 100644
--- a/src/main/java/se/kth/spoon_compare/Main.java
+++ b/src/main/java/se/kth/spoon_compare/Main.java
@@ -12,7 +12,7 @@ public class Main {
public static void main(String[] args) {
- MavenLogAnalyzer mavenLog = new MavenLogAnalyzer(new File("/Users/frank/Documents/Work/PHD/Explaining/breaking-good/0abf7148300f40a1da0538ab060552bca4a2f1d8.log"));
+ MavenLogAnalyzer mavenLog = new MavenLogAnalyzer(new File("/Users/frank/Documents/Work/PHD/Explaining/breaking-good/projects/0abf7148300f40a1da0538ab060552bca4a2f1d8/biapi/0abf7148300f40a1da0538ab060552bca4a2f1d8.log"));
try {
@@ -21,11 +21,11 @@ public static void main(String[] args) {
log.getErrorInfo().forEach((k, v) -> {
System.out.println(k);
- v.forEach(System.out::println);
+// v.forEach(System.out::println);
SpoonAnalyzer spoonAnalyzer = new SpoonAnalyzer("net.sf.jasperreports", v);
List results = spoonAnalyzer.applySpoon(proyect + k);
- results.forEach(System.out::println);
+// results.forEach(System.out::println);
});