From ac4c74dab5f92b685e0b9c4de88a0afefb12823f Mon Sep 17 00:00:00 2001 From: Samantha Perry Date: Tue, 24 Sep 2024 19:49:06 -0700 Subject: [PATCH 01/11] Fix maxADS typo --- .../java/edu/university/ecs/lab/detection/DetectionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java index d6b4a725..bd869f20 100644 --- a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java @@ -44,7 +44,7 @@ public class DetectionService { */ private static final String[] columnLabels = new String[]{"Commit ID", "Greedy Microservices", "Hub-like Microservices", "Service Chains (MS level)", "Service Chains (Method level)", "Wrong Cuts", "Cyclic Dependencies (MS level)", "Cyclic Dependencies (Method level)", "Wobbly Service Interactions", "No Healthchecks", - "No API Gateway", "maxAIS", "avgAIS", "stdAIS", "maxADC", "ADCS", "stdADS", "maxACS", "avgACS", "stdACS", "SCF", "SIY", "maxSC", "avgSC", + "No API Gateway", "maxAIS", "avgAIS", "stdAIS", "maxADS", "ADCS", "stdADS", "maxACS", "avgACS", "stdACS", "SCF", "SIY", "maxSC", "avgSC", "stdSC", "SCCmodularity", "maxSIDC", "avgSIDC", "stdSIDC", "maxSSIC", "avgSSIC", "stdSSIC", "maxLOMLC", "avgLOMLC", "stdLOMLC", "AR3 (System)","AR4 (System)", "AR6 (Delta)", "AR20 (System)"}; From fcc650b3e7816496a21b7e156c1b4f3ad57b1ba8 Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Wed, 25 Sep 2024 09:04:56 -0500 Subject: [PATCH 02/11] ServiceChainMSLevel Fix --- .../ecs/lab/common/models/ir/JClass.java | 13 ++++ .../services/ServiceChainMSLevelService.java | 62 ++++++++++------ .../unit/antipatterns/ServiceChainTest.java | 70 ++++++++++++++++++- 3 files changed, 123 insertions(+), 22 deletions(-) diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java b/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java index 6a46a8da..dd529aa4 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java @@ -53,6 +53,19 @@ public class JClass extends ProjectFile implements JsonSerializable { */ private Set methodCalls; + public JClass(String name, String path, String packageName, ClassRole classRole) { + this.name = name; + this.packageName = packageName; + this.path = path; + this.classRole = classRole; + this.methods = new HashSet<>(); + this.fields = new HashSet<>(); + this.annotations = new HashSet<>(); + this.methodCalls = new HashSet<>(); + this.implementedTypes = new HashSet<>(); + this.fileType = FileType.JCLASS; + } + public JClass(String name, String path, String packageName, ClassRole classRole, Set methods, Set fields, Set classAnnotations, Set methodCalls, Set implementedTypes) { this.name = name; this.packageName = packageName; diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java index c5cb9af1..78525fa0 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java @@ -7,31 +7,31 @@ import java.util.*; /** - * Service class for detecting and managing service chains in a network graph. + * ServiceChainMSLevelService detects service chains in a microservice architecture using DFS. */ public class ServiceChainMSLevelService { /** - * Length of the chain to consider an anti-pattern. + * Default chain length to consider as an anti-pattern. */ protected static final int DEFAULT_CHAIN_LENGTH = 3; private final int CHAIN_LENGTH; - private Set visited = new HashSet<>(); private List> allChains = new ArrayList<>(); private ServiceDependencyGraph graph = null; private List currentPath = new ArrayList<>(); + private boolean hasCycle; // Flag to indicate if a cycle has been detected in the current path /** - * Constructs the service with a default chain length of 3. + * Constructs the service with the default chain length of 3. */ public ServiceChainMSLevelService() { this.CHAIN_LENGTH = DEFAULT_CHAIN_LENGTH; } - + /** * Constructs the service with a specified chain length. - * - * @param CHAIN_LENGTH the length of the chain to consider an anti-pattern + * + * @param CHAIN_LENGTH the length of the chain to consider an anti-pattern. */ public ServiceChainMSLevelService(int CHAIN_LENGTH) { this.CHAIN_LENGTH = CHAIN_LENGTH; @@ -40,37 +40,57 @@ public ServiceChainMSLevelService(int CHAIN_LENGTH) { /** * Retrieves all service chains from the given network graph. * - * @param graph the network graph to analyze - * @return a ServiceChain object representing all detected service chains + * @param graph the network graph to analyze. + * @return a ServiceChain object representing all detected service chains. */ public ServiceChain getServiceChains(ServiceDependencyGraph graph) { allChains = new ArrayList<>(); this.graph = graph; - visited = new HashSet<>(); - graph.vertexSet().stream().filter(node -> !visited.contains(node)).forEach(node -> { - this.currentPath = new ArrayList<>(); - dfs(node); - }); + // Traverse every node in the graph, starting a DFS from each node + for (Microservice node : graph.vertexSet()) { + currentPath = new ArrayList<>(); + hasCycle = false; // Reset the cycle detection flag for each DFS call + dfs(node, new HashSet<>()); // Pass a new empty set for each DFS path to detect cycles + } + // Return the detected service chains wrapped in a ServiceChain object. return new ServiceChain(allChains); } /** - * Depth-first search (DFS) to explore and detect service chains starting from currentNode. + * Depth-first search (DFS) to explore and detect service chains starting from the currentNode. + * Detects cycles by ensuring that the current node is not already in the current DFS path. * - * @param currentNode the current node being visited + * @param currentNode the current node being visited in the DFS. + * @param pathVisited a set to track nodes visited in the current DFS path (for cycle detection). */ - private void dfs(Microservice currentNode) { + private void dfs(Microservice currentNode, Set pathVisited) { + // If the current node is in the current DFS path, there's a cycle + if (pathVisited.contains(currentNode)) { + hasCycle = true; // Mark that a cycle was detected in this path + return; // Stop processing this path due to a cycle + } + + // Add the current node to the current path and mark it visited in the DFS path currentPath.add(currentNode.getName()); - visited.add(currentNode); + pathVisited.add(currentNode); - graph.getAdjacency(currentNode).stream().filter(neighbor -> !visited.contains(neighbor)).forEach(this::dfs); + // Recur for all adjacent nodes (neighbors) of the current node + for (Microservice neighbor : graph.getAdjacency(currentNode)) { + dfs(neighbor, pathVisited); // Recursive DFS call for all neighbors + } - if (currentPath.size() >= CHAIN_LENGTH) { - allChains.add(new ArrayList<>(currentPath)); + // If the current path reaches or exceeds the chain length and no cycle was detected, add it to the list of service chains + if (currentPath.size() >= CHAIN_LENGTH && !hasCycle) { + allChains.add(new ArrayList<>(currentPath)); // Add a copy of the current path } + // Backtrack: remove the current node from the path and the DFS path set currentPath.remove(currentPath.size() - 1); + pathVisited.remove(currentNode); } } + + + diff --git a/src/test/java/unit/antipatterns/ServiceChainTest.java b/src/test/java/unit/antipatterns/ServiceChainTest.java index a2b963ac..d3e236ca 100644 --- a/src/test/java/unit/antipatterns/ServiceChainTest.java +++ b/src/test/java/unit/antipatterns/ServiceChainTest.java @@ -2,13 +2,15 @@ import static org.junit.jupiter.api.Assertions.*; +import edu.university.ecs.lab.common.models.enums.ClassRole; +import edu.university.ecs.lab.common.models.enums.HttpMethod; +import edu.university.ecs.lab.common.models.ir.*; import edu.university.ecs.lab.detection.antipatterns.services.ServiceChainMSLevelService; import org.junit.Before; import org.junit.Test; import java.util.*; -import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.models.sdg.ServiceDependencyGraph; import edu.university.ecs.lab.common.utils.FileUtils; import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; @@ -73,4 +75,70 @@ public void testEmptyGraph() { public void testNullGraph() { assertThrows(NullPointerException.class, () -> serviceChainService.getServiceChains(null)); } + + @Test + public void serviceChainHasOne() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + ServiceChainMSLevelService scs1 = new ServiceChainMSLevelService(); + + ServiceChain sc1 = scs1.getServiceChains(sdg1); + + assertTrue(sc1.getChain().size() == 1); + assertEquals(sc1.getChain().get(0), List.of("ms3", "ms2", "ms1")); + } + + @Test + public void serviceChainHasNoneCycle() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); + jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); + microservice3.addJClass(jClass6); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + ServiceChainMSLevelService scs1 = new ServiceChainMSLevelService(); + + ServiceChain sc1 = scs1.getServiceChains(sdg1); + assertTrue(sc1.getChain().isEmpty()); + + } } From cebc5dde5bda9e7b04c2ba9850c69ce5577c5926 Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Wed, 25 Sep 2024 09:39:42 -0500 Subject: [PATCH 03/11] StructuralCoupling negative values bug --- .../detection/metrics/models/StructuralCoupling.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java index c880d2a5..221b5fcd 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java @@ -81,11 +81,21 @@ public StructuralCoupling(ServiceDependencyGraph graph) { graph.getAllEdges(vertexA, vertexB).stream().mapToDouble(graph::getEdgeWeight).sum()); in_degree.put(pair, graph.getAllEdges(vertexB, vertexA).stream().mapToDouble(graph::getEdgeWeight).sum()); + } + } + } + + for (Microservice vertexA: graph.vertexSet()) { + for (Microservice vertexB: graph.vertexSet()) { + List pair = Arrays.asList(vertexA.getName(), vertexB.getName()); + if (!vertexA.equals(vertexB) && graph.containsEdge(vertexA, vertexB)) { degree.put(pair, in_degree.get(pair)+out_degree.get(pair)); LWF.put(pair, (1+out_degree.get(pair))/(1+degree.get(pair))); } } } + + double max_degree; try { max_degree = degree.values().stream().max(Comparator.comparingDouble(Double::doubleValue)). @@ -104,7 +114,7 @@ public StructuralCoupling(ServiceDependencyGraph graph) { } for (List pair: LWF.keySet()) { GWF.put(pair, degree.get(pair)/max_degree); - SC.put(pair, 1-1/degree.get(pair)-LWF.get(pair)*GWF.get(pair)); + SC.put(pair, 1-(1/degree.get(pair))*LWF.get(pair)*GWF.get(pair)); } // Amount of actually connected pairs From 4fb6b2a323bc79bef3975005b2001efe5e882d10 Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Thu, 26 Sep 2024 13:00:32 -0500 Subject: [PATCH 04/11] Refactored DetectionService naming approach --- .../lab/common/utils/JsonReadWriteUtils.java | 17 ++-- .../services/DeltaExtractionService.java | 96 ++----------------- .../ecs/lab/detection/DetectionService.java | 26 ++--- .../ecs/lab/detection/ExcelOutputRunner.java | 2 +- .../create/services/IRExtractionService.java | 2 +- .../lab/intermediate/merge/IRMergeRunner.java | 4 +- .../merge/services/MergeService.java | 11 ++- 7 files changed, 42 insertions(+), 116 deletions(-) diff --git a/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java index 96bc7560..acfd44a2 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java @@ -11,6 +11,9 @@ import edu.university.ecs.lab.common.models.serialization.ProjectFileDeserializer; import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Optional; /** @@ -31,13 +34,15 @@ private JsonReadWriteUtils() { * @param filePath the file path where the JSON should be saved */ public static void writeToJSON(String filePath, T object) { - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.setPrettyPrinting(); - Gson gson = gsonBuilder.create(); - try (Writer writer = new BufferedWriter(new FileWriter(filePath))) { - gson.toJson(object, writer); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + try { + Path path = Paths.get(filePath); + Files.createDirectories(path.getParent()); + try (Writer writer = Files.newBufferedWriter(path)) { + gson.toJson(object, writer); + } } catch (IOException e) { - Error.reportAndExit(Error.INVALID_JSON_READ, Optional.of(e)); + Error.reportAndExit(Error.INVALID_JSON_WRITE, Optional.of(e)); } } diff --git a/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java b/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java index 17210f94..91c6de32 100644 --- a/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java +++ b/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java @@ -3,11 +3,8 @@ import com.google.gson.JsonObject; import edu.university.ecs.lab.common.config.Config; import edu.university.ecs.lab.common.config.ConfigUtil; -import edu.university.ecs.lab.common.error.Error; import edu.university.ecs.lab.common.models.ir.ConfigFile; import edu.university.ecs.lab.common.models.ir.JClass; -import edu.university.ecs.lab.common.models.ir.Microservice; -import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.services.GitService; import edu.university.ecs.lab.common.services.LoggerManager; import edu.university.ecs.lab.common.utils.FileUtils; @@ -18,13 +15,8 @@ import edu.university.ecs.lab.delta.models.enums.ChangeType; import org.eclipse.jgit.diff.DiffEntry; -import javax.json.Json; import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.Optional; /** * Service for extracting the differences between two commits of a repository. @@ -53,42 +45,36 @@ public class DeltaExtractionService { */ private final String commitNew; - /** - * The old IR for validating delta file changes - */ -// private final MicroserviceSystem oldSystem; - /** * System change object that will be returned */ private SystemChange systemChange; /** - * Represents a list of diff entries that are related to pom.xml add or delete + * The type of change that is made */ -// private final List pomDiffs; + private ChangeType changeType; /** - * The type of change that is made + * The path to the output file */ - private ChangeType changeType; + private String outputPath; /** * Constructor for the DeltaExtractionService * * @param configPath path to the config file - * @param oldIRPath path to the oldIR + * @param outputPath output path for file * @param commitOld old commit for comparison * @param commitNew new commit for comparison */ - public DeltaExtractionService(String configPath, String oldIRPath, String commitOld, String commitNew) { + public DeltaExtractionService(String configPath, String outputPath, String commitOld, String commitNew) { this.config = ConfigUtil.readConfig(configPath); this.gitService = new GitService(configPath); this.commitOld = commitOld; this.commitNew = commitNew; -// this.oldSystem = JsonReadWriteUtils.readFromJSON(oldIRPath, MicroserviceSystem.class); -// pomDiffs = new ArrayList<>(); + this.outputPath = outputPath.isEmpty() ? "./Delta.json" : outputPath; } /** @@ -152,19 +138,8 @@ public void processDelta(List diffEntries) { } - changeType = ChangeType.fromDiffEntry(entry); - // Special check for pom file manipulations only -// if(path.endsWith("/pom.xml")) { -// // Get some configuration change -// data = configurationChange(entry, oldPath, newPath, path); -// -// if(data == null) { -// continue; -// } -// } else { - switch(changeType) { case ADD: data = add(newPath); @@ -176,19 +151,11 @@ public void processDelta(List diffEntries) { data = delete(); } -// if(data == null) { -// continue; -// } -// } - - systemChange.getChanges().add(new Delta(oldPath, newPath, changeType, data)); } - String filePath = "./output/Delta_" + commitOld.substring(0, 4) + "_" + commitNew.substring(0, 4) + ".json"; - // Output the system changes - JsonReadWriteUtils.writeToJSON(filePath, systemChange); + JsonReadWriteUtils.writeToJSON(outputPath, systemChange); // Report LoggerManager.info(() -> "Delta changes extracted between " + commitOld + " -> " + commitNew); @@ -225,53 +192,6 @@ private JsonObject add(String newPath) { } - /** - * This method parses modified files and handles additional logic related to orphan management - * - * [!] Note: Due to the management of orphans we will do special checks here. - * - * @param path the file path that will be parsed - * @return - */ -// private JsonObject modify(String oldPath) { -// JsonObject jsonObject = null; -// JClass jClass = null; -// -// if(FileUtils.isConfigurationFile(oldPath)) { -// // Special check for modifying a pom that was previously filtered out for being too general -// if(oldPath.endsWith("pom.xml") && oldSystem.findFile(oldPath) == null) { -// return null; -// } -// jsonObject = add(oldPath); -// } else { -// jClass = SourceToObjectUtils.parseClass(new File(FileUtils.gitPathToLocalPath(oldPath, config.getRepoName())), config, ""); -// } -// -// if(jClass != null) { -// jsonObject = jClass.toJsonObject(); -// } -// -// // Similar to add check, but if we couldn't parse and the class exists in old system we must allow it -// if (jsonObject == null && oldSystem.findFile(oldPath) == null) { -// return null; -// // If the class is unparsable and the class exists in the old system we must delete it now -// } else if (jsonObject == null && oldSystem.findFile(oldPath) != null) { -// changeType = ChangeType.DELETE; -// return delete(); -// // If the class is parsable and the class doesn't exist in the old system we must add it now -// } else if (jsonObject != null && oldSystem.findFile(oldPath) == null) { -// changeType = ChangeType.ADD; -// return jsonObject; -// } else if(jsonObject == null) { -// System.err.println("No file found for " + oldPath); -// System.exit(1); -// } -// -// -// -// return jsonObject; -// } - /** * This method returns a blank JsonObject() as there is no data to parse * diff --git a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java index bd869f20..5a846927 100644 --- a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java @@ -55,9 +55,9 @@ public class DetectionService { private static final int METRICS = 24; private static final int ARCHRULES = 4; - // private static final String OLD_IR_PATH = "./output/OldIR_"; - private static final String DELTA_PATH = "./output/Delta_"; - // private static final String NEW_IR_PATH = "./output/NewIR_"; + private static final String BASE_DELTA_PATH = "./output/Delta/Delta_"; + private static final String BASE_IR_PATH = "./output/IR/IR_"; + /** @@ -101,7 +101,7 @@ public void runDetection() { // Generate the initial IR irExtractionService = new IRExtractionService(configPath, Optional.of(commits.get(0).toString().split(" ")[1])); String firstCommit = commits.get(0).getName().substring(0, 4); - irExtractionService.generateIR("IR_" + firstCommit + ".json"); + irExtractionService.generateIR(BASE_IR_PATH + firstCommit + ".json"); // Setup sheet and headers sheet = workbook.createSheet(config.getSystemName()); @@ -130,27 +130,27 @@ public void runDetection() { // Set the commitID as the first cell value Cell commitIdCell = row.createCell(0); - commitIdCell.setCellValue(commitIdOld.substring(0, 7)); + commitIdCell.setCellValue(commitIdOld); // Read in the old system - String oldSysPath = "./output/IR_" + commitIdOld.substring(0, 4) +".json"; - MicroserviceSystem oldSystem = JsonReadWriteUtils.readFromJSON(oldSysPath, MicroserviceSystem.class); + String oldIRPath = BASE_IR_PATH + commitIdOld.substring(0, 4) +".json"; + MicroserviceSystem oldSystem = JsonReadWriteUtils.readFromJSON(oldIRPath, MicroserviceSystem.class); // Extract changes from one commit to the other if(i < commits.size() - 1) { String commitIdNew = commits.get(i + 1).toString().split(" ")[1]; - String newSysPath = "./output/IR_" + commitIdNew.substring(0, 4) +".json"; - String deltaPath = DELTA_PATH + commitIdOld.substring(0, 4) + "_" + commitIdNew.substring(0, 4) + ".json"; + String newIRPath = BASE_IR_PATH + commitIdNew.substring(0, 4) +".json"; + String deltaPath = BASE_DELTA_PATH + commitIdOld.substring(0, 4) + "_" + commitIdNew.substring(0, 4) + ".json"; - deltaExtractionService = new DeltaExtractionService(configPath, oldSysPath, commitIdOld, commitIdNew); + deltaExtractionService = new DeltaExtractionService(configPath, deltaPath, commitIdOld, commitIdNew); deltaExtractionService.generateDelta(); // Merge Delta changes to old IR to create new IR representing new commit changes - MergeService mergeService = new MergeService(oldSysPath, deltaPath, configPath); + MergeService mergeService = new MergeService(oldIRPath, deltaPath, configPath, newIRPath); mergeService.generateMergeIR(commitIdNew.substring(0, 4)); // Read in the new system and system change - newSystem = JsonReadWriteUtils.readFromJSON(newSysPath, MicroserviceSystem.class); + newSystem = JsonReadWriteUtils.readFromJSON(newIRPath, MicroserviceSystem.class); systemChange = JsonReadWriteUtils.readFromJSON(deltaPath, SystemChange.class); } @@ -294,7 +294,7 @@ private void detectMetrics(MicroserviceSystem microserviceSystem, Map microservices, String fileName) { MicroserviceSystem microserviceSystem = new MicroserviceSystem(config.getSystemName(), commitID, microservices, new HashSet<>()); - JsonReadWriteUtils.writeToJSON("./output/" + fileName, microserviceSystem.toJsonObject()); + JsonReadWriteUtils.writeToJSON(fileName, microserviceSystem.toJsonObject()); LoggerManager.info(() -> "Successfully extracted IR at " + commitID); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java b/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java index 6059520a..5ab74354 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java @@ -25,13 +25,13 @@ public static void main(String[] args) throws IOException { String[] finalArgs = args; LoggerManager.info(() -> "IRMergeRunner starting... args: " + Arrays.toString(finalArgs)); - if (args.length != 3) { + if (args.length != 4) { Error.reportAndExit(Error.INVALID_ARGS, Optional.empty()); } Config config = ConfigUtil.readConfig(args[0]); - MergeService mergeService = new MergeService(args[0], args[1], args[2]); + MergeService mergeService = new MergeService(args[0], args[1], args[2], args[3]); //mergeService.generateMergeIR(); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java b/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java index 6b9cb884..2b58b869 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java @@ -23,15 +23,18 @@ public class MergeService { private final Config config; private final MicroserviceSystem microserviceSystem; private final SystemChange systemChange; + private final String outputPath; // TODO handle exceptions here public MergeService( String intermediatePath, String deltaPath, - String configPath) { + String configPath, + String outputPath) { this.config = ConfigUtil.readConfig(configPath); this.microserviceSystem = JsonReadWriteUtils.readFromJSON(Path.of(intermediatePath).toAbsolutePath().toString(), MicroserviceSystem.class); this.systemChange = JsonReadWriteUtils.readFromJSON(Path.of(deltaPath).toAbsolutePath().toString(), SystemChange.class); + this.outputPath = outputPath.isEmpty() ? "./NewIR.json" : outputPath; } /** @@ -42,8 +45,7 @@ public void generateMergeIR(String newCommitID) { // If no changes are present we will write back out same IR if (Objects.isNull(systemChange.getChanges())) { LoggerManager.debug(() -> "No changes found at " + systemChange.getOldCommit() + " -> " + systemChange.getNewCommit()); - String filePath = "./output/IR_" + newCommitID + ".json"; - JsonReadWriteUtils.writeToJSON(filePath, microserviceSystem); + JsonReadWriteUtils.writeToJSON(outputPath, microserviceSystem); return; } @@ -70,8 +72,7 @@ public void generateMergeIR(String newCommitID) { microserviceSystem.setCommitID(systemChange.getNewCommit()); LoggerManager.info(() -> "Merged to new IR at " + systemChange.getNewCommit()); - String filePath = "./output/IR_" + newCommitID + ".json"; - JsonReadWriteUtils.writeToJSON(filePath, microserviceSystem); + JsonReadWriteUtils.writeToJSON(outputPath, microserviceSystem); } From a918e2557400043ad71df57ac889653a23277efc Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Thu, 26 Sep 2024 13:11:45 -0500 Subject: [PATCH 05/11] Refactored DetectionService naming approach --- .../ecs/lab/detection/DetectionService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java index 5a846927..92a9d434 100644 --- a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java @@ -55,8 +55,8 @@ public class DetectionService { private static final int METRICS = 24; private static final int ARCHRULES = 4; - private static final String BASE_DELTA_PATH = "./output/Delta/Delta_"; - private static final String BASE_IR_PATH = "./output/IR/IR_"; + private static final String BASE_DELTA_PATH = "./output/Delta/Delta"; + private static final String BASE_IR_PATH = "./output/IR/IR"; @@ -101,7 +101,7 @@ public void runDetection() { // Generate the initial IR irExtractionService = new IRExtractionService(configPath, Optional.of(commits.get(0).toString().split(" ")[1])); String firstCommit = commits.get(0).getName().substring(0, 4); - irExtractionService.generateIR(BASE_IR_PATH + firstCommit + ".json"); + irExtractionService.generateIR(BASE_IR_PATH + "1_" + firstCommit + ".json"); // Setup sheet and headers sheet = workbook.createSheet(config.getSystemName()); @@ -133,14 +133,14 @@ public void runDetection() { commitIdCell.setCellValue(commitIdOld); // Read in the old system - String oldIRPath = BASE_IR_PATH + commitIdOld.substring(0, 4) +".json"; + String oldIRPath = BASE_IR_PATH + (i+1) + "_" + commitIdOld.substring(0, 4) +".json"; MicroserviceSystem oldSystem = JsonReadWriteUtils.readFromJSON(oldIRPath, MicroserviceSystem.class); // Extract changes from one commit to the other if(i < commits.size() - 1) { String commitIdNew = commits.get(i + 1).toString().split(" ")[1]; - String newIRPath = BASE_IR_PATH + commitIdNew.substring(0, 4) +".json"; - String deltaPath = BASE_DELTA_PATH + commitIdOld.substring(0, 4) + "_" + commitIdNew.substring(0, 4) + ".json"; + String newIRPath = BASE_IR_PATH + (i+2) + "_" + commitIdNew.substring(0, 4) +".json"; + String deltaPath = BASE_DELTA_PATH + (i+1) + "_" + commitIdOld.substring(0, 4) + "_" + commitIdNew.substring(0, 4) + ".json"; deltaExtractionService = new DeltaExtractionService(configPath, deltaPath, commitIdOld, commitIdNew); deltaExtractionService.generateDelta(); @@ -162,7 +162,7 @@ public void runDetection() { // We can detect/update if there are >= 1 microservices if (Objects.nonNull(oldSystem.getMicroservices()) && !oldSystem.getMicroservices().isEmpty()) { detectAntipatterns(oldSystem, antipatterns); - detectMetrics(oldSystem, metrics, commitIdOld.substring(0, 4)); + detectMetrics(oldSystem, metrics, oldIRPath); updateAntiPatterns(currIndex, antipatterns); updateMetrics(currIndex, metrics); @@ -265,7 +265,7 @@ private void detectAntipatterns(MicroserviceSystem microserviceSystem, Map metrics, String commitID) { + private void detectMetrics(MicroserviceSystem microserviceSystem, Map metrics, String oldIRPath) { // Create SDG ServiceDependencyGraph sdg = new ServiceDependencyGraph(microserviceSystem); @@ -294,7 +294,7 @@ private void detectMetrics(MicroserviceSystem microserviceSystem, Map Date: Thu, 26 Sep 2024 13:55:12 -0500 Subject: [PATCH 06/11] Cyclic testing --- .../antipatterns/CyclicDependencyTest.java | 187 ++++++++++++++++-- 1 file changed, 171 insertions(+), 16 deletions(-) diff --git a/src/test/java/unit/antipatterns/CyclicDependencyTest.java b/src/test/java/unit/antipatterns/CyclicDependencyTest.java index 70dfd3d1..7183361f 100644 --- a/src/test/java/unit/antipatterns/CyclicDependencyTest.java +++ b/src/test/java/unit/antipatterns/CyclicDependencyTest.java @@ -3,16 +3,17 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import edu.university.ecs.lab.common.models.enums.ClassRole; +import edu.university.ecs.lab.common.models.enums.HttpMethod; +import edu.university.ecs.lab.common.models.ir.*; +import edu.university.ecs.lab.detection.antipatterns.models.ServiceChain; import edu.university.ecs.lab.detection.antipatterns.services.CyclicDependencyMSLevelService; +import edu.university.ecs.lab.detection.antipatterns.services.ServiceChainMSLevelService; import org.junit.Before; import org.junit.Test; -import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.models.sdg.ServiceDependencyGraph; import edu.university.ecs.lab.common.utils.FileUtils; import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; @@ -26,17 +27,17 @@ public class CyclicDependencyTest { @Before public void setUp(){ - FileUtils.makeDirs(); - - IRExtractionService irExtractionService = new IRExtractionService(Constants.TEST_CONFIG_PATH, Optional.empty()); - - irExtractionService.generateIR("TestIR.json"); - - MicroserviceSystem microserviceSystem = JsonReadWriteUtils.readFromJSON("./output/TestIR.json", MicroserviceSystem.class); - - sdg = new ServiceDependencyGraph(microserviceSystem); - - cyclicService = new CyclicDependencyMSLevelService(); +// FileUtils.makeDirs(); +// +// IRExtractionService irExtractionService = new IRExtractionService(Constants.TEST_CONFIG_PATH, Optional.empty()); +// +// irExtractionService.generateIR("TestIR.json"); +// +// MicroserviceSystem microserviceSystem = JsonReadWriteUtils.readFromJSON("./output/TestIR.json", MicroserviceSystem.class); +// +// sdg = new ServiceDependencyGraph(microserviceSystem); +// +// cyclicService = new CyclicDependencyMSLevelService(); } public void testCyclicDependencyDetection(){ @@ -50,5 +51,159 @@ public void testCyclicDependencyDetection(){ assertTrue(Objects.equals(cyclicDep.getCycles(), expectedCyclicDep)); } + + @Test + public void cyclicDependencyHasNOne() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 0); + + } + + @Test + public void cyclicDependencyHasOne() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); + jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); + microservice3.addJClass(jClass6); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 1); + assertTrue(cd.getCycles().get(0).size() == 3); + assertTrue(cd.getCycles().get(0).containsAll(List.of("ms1", "ms2", "ms3"))); + + } + + @Test + public void cyclicDependencyHasOneAlso() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass2 = new JClass("class5", "/class2","class5", ClassRole.SERVICE); + jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice1.addJClass(jClass2); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass3 = new JClass("class2", "/class3","class2", ClassRole.SERVICE); + jClass3.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass3); + JClass jClass4 = new JClass("class3", "/class4","class3", ClassRole.CONTROLLER); + jClass4.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass4); + + + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 1); + assertTrue(cd.getCycles().get(0).size() == 2); + assertTrue(cd.getCycles().get(0).containsAll(List.of("ms1", "ms2"))); + + } + + @Test + public void cyclicDependencyHasTwo() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + JClass jClass9 = new JClass("class9", "/class9","class9", ClassRole.SERVICE); + jClass9.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint4", HttpMethod.GET))); + microservice3.addJClass(jClass9); + JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); + jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); + microservice3.addJClass(jClass6); + + Microservice microservice4 = new Microservice("ms4", "/ms4"); + JClass jClass7 = new JClass("jClass7", "/jClass7","jClass7", ClassRole.SERVICE); + jClass7.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice4.addJClass(jClass7); + JClass jClass8 = new JClass("jClass8", "/jClass8","jClass8", ClassRole.CONTROLLER); + jClass8.setMethods(Set.of(new Endpoint(new Method(), "/endpoint4", HttpMethod.GET))); + microservice4.addJClass(jClass8); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3, microservice4), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 2); + assertTrue(cd.getCycles().get(0).size() == 3); + assertTrue(cd.getCycles().get(1).size() == 2); + assertTrue(cd.getCycles().get(0).containsAll(List.of("ms1", "ms2", "ms3"))); + assertTrue(cd.getCycles().get(1).containsAll(List.of("ms3", "ms4"))); + + } } From c1309a2717f64ed937d036dc4e7a3a249f833b16 Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Tue, 1 Oct 2024 11:05:37 -0500 Subject: [PATCH 07/11] Bulk Changes --- .gitignore | 1 + config.json | 6 +- scripts/parser2.js | 120 +++++++++ .../common/models/enums/EndpointTemplate.java | 23 +- .../common/models/enums/RestCallTemplate.java | 49 ++-- .../ecs/lab/common/models/ir/Annotation.java | 54 +++- .../ecs/lab/common/models/ir/Endpoint.java | 6 +- .../ecs/lab/common/models/ir/JClass.java | 18 +- .../ecs/lab/common/models/ir/Method.java | 23 +- .../lab/common/models/ir/Microservice.java | 19 +- .../ecs/lab/common/models/ir/Parameter.java | 49 ++++ .../ecs/lab/common/models/ir/RestCall.java | 37 ++- .../serialization/MethodCallDeserializer.java | 4 + .../serialization/MethodDeserializer.java | 11 +- .../ecs/lab/common/services/GitService.java | 152 +++++------ .../lab/common/utils/JsonReadWriteUtils.java | 2 +- .../lab/common/utils/SourceToObjectUtils.java | 59 +++-- .../ecs/lab/detection/DetectionService.java | 13 +- ...iPattern.java => AbstractAntiPattern.java} | 2 +- .../antipatterns/models/CyclicDependency.java | 2 +- .../models/GreedyMicroservice.java | 2 +- .../models/HubLikeMicroservice.java | 2 +- .../antipatterns/models/NoApiGateway.java | 2 +- .../antipatterns/models/NoHealthcheck.java | 2 +- .../antipatterns/models/ServiceChain.java | 2 +- .../models/WobblyServiceInteraction.java | 2 +- .../antipatterns/models/WrongCuts.java | 2 +- .../CyclicDependencyMSLevelService.java | 9 +- .../WobblyServiceInteractionService.java | 1 + .../detection/architecture/models/AR4.java | 12 +- .../detection/architecture/models/AR6.java | 79 ++++-- .../detection/architecture/models/AR7.java | 81 ++++-- .../services/ARDetectionService.java | 16 +- .../detection/metrics/RunCohesionMetrics.java | 23 +- .../metrics/models/DegreeCoupling.java | 2 +- .../detection/metrics/models/Operation.java | 6 +- .../metrics/models/StructuralCoupling.java | 4 +- .../services/LackOfMessageLevelCohesion.java | 4 +- .../ServiceInterfaceDataCohesion.java | 5 +- .../StrictServiceImplementationCohesion.java | 4 +- .../java/integration/IRComparisonTest.java | 246 +++++++++--------- .../antipatterns/CyclicDependencyTest.java | 26 +- .../unit/antipatterns/ServiceChainTest.java | 10 +- .../java/unit/extraction/ExtractionTest.java | 85 +++--- src/test/resources/TestFile2.java | 108 ++++++++ src/test/resources/TestFile3.java | 30 +++ src/test/resources/test_config.json | 4 +- 47 files changed, 961 insertions(+), 458 deletions(-) create mode 100644 scripts/parser2.js create mode 100644 src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java rename src/main/java/edu/university/ecs/lab/detection/antipatterns/models/{AntiPattern.java => AbstractAntiPattern.java} (91%) create mode 100644 src/test/resources/TestFile2.java create mode 100644 src/test/resources/TestFile3.java diff --git a/.gitignore b/.gitignore index 155021a4..c52f1c99 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ commands.txt /clone/ /.vscode /logs +/node_modules/ diff --git a/config.json b/config.json index 2d86cccc..ba90e41e 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "systemName": "Train-ticket", - "repositoryURL": "https://github.com/FudanSELab/train-ticket.git", - "baseBranch": "main" + "systemName": "sample-spring-microservices-new", + "repositoryURL": "https://github.com/piomin/sample-spring-microservices-new.git", + "baseBranch": "master" } \ No newline at end of file diff --git a/scripts/parser2.js b/scripts/parser2.js new file mode 100644 index 00000000..9a8d4b6b --- /dev/null +++ b/scripts/parser2.js @@ -0,0 +1,120 @@ +const babel = require("@babel/core"); +const traverse = require("@babel/traverse").default; +const t = require("@babel/types"); + +const jsCode = ` +'use strict'; + +/** + * @ngdoc service + * @name alertApp.dataService + * @description + * # dataService + * Factory in the alertApp. + */ +angular.module('oauthApp') + .factory('dataService', function ($http, $q) { + var userApi = '/user-service/'; + var taskApi = '/task-service/'; + var loggedInUserApi = '/api/loggedinuser/me'; + + var makeRestCall = function (url) { + return $http.get(url) + .then(function (response) { + if (typeof response.data === 'object') { + return response.data; + } else { + return $q.reject(response.data); + } + }, function (response) { + return $q.reject(response.data); + }); + }; + + return { + getAllUserData: function () { + return makeRestCall(userApi); + }, + getAllTaskData: function () { + return makeRestCall(taskApi); + }, + getUserDataByUserName: function (userName) { + return makeRestCall(userApi + userName); + }, + getTaskDataByTaskId: function (taskId) { + return makeRestCall(taskApi + taskId); + }, + getTaskDataByUserName: function (userName) { + return makeRestCall(taskApi + 'usertask' + '/' + userName); + }, + getLoggedInUser: function () { + return makeRestCall(loggedInUserApi); + } + }; + }); +`; + +// Transform the code to get the AST +const output = babel.transform(jsCode, { + ast: true, + code: false, + presets: ["@babel/preset-env"], +}); + +// Map to store variable values +const variableValues = {}; + +// Function to check if a call is an API call ($http.get or similar) +function isApiCall(node) { + return ( + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, { name: "$http" }) && + (t.isIdentifier(node.callee.property, { name: "get" }) || + t.isIdentifier(node.callee.property, { name: "post" })) + ); +} + +function resolveValue(node) { + if (t.isLiteral(node)) { + // Return literal value + return node.value; + } else if (t.isIdentifier(node)) { + // Check if the variable is known + return variableValues[node.name] || '{?}'; + } else if (t.isBinaryExpression(node) && node.operator === "+") { + // Resolve both sides of the binary expression (concatenation) + const left = resolveValue(node.left); + const right = resolveValue(node.right); + return left + right; + } else if (node.type === "CallExpression") { + // Handle function calls as unknown + return '{?}'; + } else { + // Handle any other case as unknown + return '{?}'; + } +} + +// Traverse the AST +traverse(output.ast, { + // Track variable declarations + VariableDeclarator(path) { + const varName = path.node.id.name; + const init = path.node.init; + if (init && (t.isLiteral(init) || t.isBinaryExpression(init))) { + // Store the value of the variable (literals or binary expressions) + variableValues[varName] = resolveValue(init); + } + }, + + + CallExpression(path) { + if (isApiCall(path.node)) { + const urlArg = path.node.arguments[0]; // First argument (URL) + const resolvedUrl = resolveValue(urlArg); // Try to resolve the argument + + console.log(`Resolved URL: ${path.node.callee.object.name}.${path.node.callee.property.name} to ${resolvedUrl}`); + } + } + +}); \ No newline at end of file diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java b/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java index fd1ef38e..aab5d24b 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java @@ -1,25 +1,20 @@ package edu.university.ecs.lab.common.models.enums; -import com.github.javaparser.ast.expr.AnnotationExpr; -import com.github.javaparser.ast.expr.MemberValuePair; -import com.github.javaparser.ast.expr.NormalAnnotationExpr; -import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.*; import edu.university.ecs.lab.intermediate.utils.StringParserUtils; import lombok.Getter; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Factory class for generating an endpoint template from annotations */ @Getter public class EndpointTemplate { -// GET_MAPPING("GetMapping", HttpMethod.GET), -// POST_MAPPING("PostMapping", HttpMethod.POST), -// DELETE_MAPPING("DeleteMapping", HttpMethod.DELETE), -// private static final EndpointTemplate PUT_MAPPING = new EndpointTemplate("PutMapping", HttpMethod.PUT); - public static final List ENDPOINT_ANNOTATIONS = Arrays.asList("RequestMapping", "GetMapping", "PutMapping", "PostMapping", "DeleteMapping", "PatchMapping"); private final HttpMethod httpMethod; private final String name; @@ -57,6 +52,10 @@ public EndpointTemplate(AnnotationExpr requestMapping, AnnotationExpr endpointMa } } else if (endpointMapping instanceof SingleMemberAnnotationExpr) { url = endpointMapping.asSingleMemberAnnotationExpr().getMemberValue().toString().replaceAll("\"", ""); + } else if(endpointMapping instanceof MarkerAnnotationExpr) { + if(preUrl.isEmpty()) { + url = "/"; + } } if(finalHttpMethod == HttpMethod.ALL) { @@ -82,12 +81,14 @@ public EndpointTemplate(AnnotationExpr requestMapping, AnnotationExpr endpointMa // Replace any double slashes finalURL = finalURL.replaceAll("//", "/"); // If it ends with a slash remove it - finalURL = finalURL.endsWith("/") ? finalURL.substring(0, finalURL.length() - 1) : finalURL; + finalURL = finalURL.endsWith("/") && !finalURL.equals("/") ? finalURL.substring(0, finalURL.length() - 1) : finalURL; + + // Get query Parameters this.httpMethod = finalHttpMethod; this.name = endpointMapping.getNameAsString(); - this.url = finalURL; + this.url = simplifyEndpointURL(finalURL); } diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java b/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java index 48eb2c0c..90975621 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.expr.*; +import edu.university.ecs.lab.common.models.ir.MethodCall; import edu.university.ecs.lab.intermediate.utils.StringParserUtils; import javassist.expr.Expr; import lombok.Getter; @@ -20,8 +21,8 @@ */ @Getter public class RestCallTemplate { - public static final Set REST_OBJECTS = Set.of("RestTemplate", "OAuth2RestOperations", "OAuth2RestTemplate"); - public static final Set REST_METHODS = Set.of("getForObject", "postForObject", "patchForObject", "put", "delete", "exchange"); + public static final Set REST_OBJECTS = Set.of("RestTemplate", "OAuth2RestOperations", "OAuth2RestTemplate", "WebClient"); + public static final Set REST_METHODS = Set.of("getForObject", "postForObject", "patchForObject", "put", "delete", "exchange", "get", "post", "options", "patch"); private static final String UNKNOWN_VALUE = "{?}"; private final String url; @@ -29,10 +30,10 @@ public class RestCallTemplate { private final CompilationUnit cu; private final MethodCallExpr mce; - public RestCallTemplate(MethodCallExpr mce, CompilationUnit cu) { + public RestCallTemplate(MethodCallExpr mce, MethodCall mc, CompilationUnit cu) { this.cu = cu; this.mce = mce; - this.url = mce.getArguments().get(0).toString().isEmpty() ? "" : cleanURL(parseURL(mce.getArguments().get(0))); + this.url = simplifyEndpointURL(preParseURL(mce, mc)); this.httpMethod = getHttpFromName(mce); } @@ -46,10 +47,13 @@ public HttpMethod getHttpFromName(MethodCallExpr mce) { String methodName = mce.getNameAsString(); switch (methodName) { case "getForObject": + case "get": return HttpMethod.GET; case "postForObject": + case "post": return HttpMethod.POST; case "patchForObject": + case "patch": return HttpMethod.PATCH; case "put": return HttpMethod.PUT; @@ -186,15 +190,30 @@ private String parseFieldValue(String fieldName) { return ""; } -// private String parseNameValue(Expression mceScope, String name) { -// for (VariableDeclarationExpr vdc : mceScope.findAll(VariableDeclarationExpr.class)) { -// for(VariableDeclarator vd : vdc.getVariables()) { -// if(vd.getNameAsString().equals(name)) { -// -// } -// } -// } -// -// return ""; -// } + + private String preParseURL(MethodCallExpr mce, MethodCall mc) { + + // Nuance for webclient with method appender pattern + if(mc.getObjectType().equals("WebClient")) { + if(mce.getParentNode().isPresent()) { + if(mce.getParentNode().get() instanceof MethodCallExpr pmce) { + return pmce.getArguments().get(0).toString().isEmpty() ? "" : cleanURL(parseURL(pmce.getArguments().get(0))); + } + } + } else { + return mce.getArguments().get(0).toString().isEmpty() ? "" : cleanURL(parseURL(mce.getArguments().get(0))); + } + + return ""; + } + + /** + * Simplifies all path arguments to {?}. + * + * @param url the endpoint URL + * @return the simplified endpoint URL + */ + public static String simplifyEndpointURL(String url) { + return url.replaceAll("\\{[^{}]*\\}", "{?}"); + } } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java index a7fc42b8..84ac1aaa 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java @@ -1,25 +1,41 @@ package edu.university.ecs.lab.common.models.ir; +import com.github.javaparser.ast.expr.*; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + /** * Represents an annotation in Java */ @Data @EqualsAndHashCode public class Annotation extends Node { - /** - * The contents of the annotation * - */ - protected String contents; - public Annotation(String name, String packageAndClassName, String contents) { + private Map attributes; + + public Annotation(AnnotationExpr annotationExpr, String packageAndClassName) { + this.name = annotationExpr.getNameAsString(); + this.packageAndClassName = packageAndClassName; + this.attributes = parseAttributes(annotationExpr); + } + + public Annotation(String name, String packageAndClassName, HashMap attributes) { this.name = name; this.packageAndClassName = packageAndClassName; - this.contents = contents; + this.attributes = attributes; + } + + public String getContents() { + return getAttributes().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")); } /** @@ -28,11 +44,35 @@ public Annotation(String name, String packageAndClassName, String contents) { @Override public JsonObject toJsonObject() { JsonObject jsonObject = new JsonObject(); + Gson gson = new Gson(); jsonObject.addProperty("name", getName()); jsonObject.addProperty("packageAndClassName", getPackageAndClassName()); - jsonObject.addProperty("contents", getContents()); + JsonElement attributesJson = gson.toJsonTree(attributes, new TypeToken>(){}.getType()); + jsonObject.add("attributes", attributesJson); return jsonObject; } + + private static HashMap parseAttributes(AnnotationExpr annotationExpr) { + HashMap attributes = new HashMap<>(); + + if(annotationExpr instanceof MarkerAnnotationExpr) { + return attributes; + } else if (annotationExpr instanceof SingleMemberAnnotationExpr smAnnotationExpr) { + if(smAnnotationExpr.getMemberValue() instanceof StringLiteralExpr sle) { + attributes.put("default", sle.asString()); + } else { + return attributes; + } + } else if (annotationExpr instanceof NormalAnnotationExpr nAnnotationExpr) { + for(MemberValuePair mvp : nAnnotationExpr.getPairs()) { + if(mvp.getValue() instanceof StringLiteralExpr sle) { + attributes.put(mvp.getNameAsString(), sle.asString()); + } + } + } + + return attributes; + } } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java index 2d48b81c..664fdc4a 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java @@ -1,11 +1,13 @@ package edu.university.ecs.lab.common.models.ir; +import com.google.gson.Gson; import com.google.gson.JsonObject; import edu.university.ecs.lab.common.models.enums.HttpMethod; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.HashSet; import java.util.Set; import java.util.List; @@ -29,7 +31,8 @@ public class Endpoint extends Method { private HttpMethod httpMethod; - public Endpoint(String methodName, String packageName, Set parameters, String returnType, Set annotations, String microserviceName, + + public Endpoint(String methodName, String packageName, Set parameters, String returnType, Set annotations, String microserviceName, String className) { super(methodName, packageName, parameters, returnType, annotations, microserviceName, className); } @@ -53,4 +56,5 @@ public JsonObject toJsonObject() { return jsonObject; } + } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java b/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java index dd529aa4..c1ebc9bf 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.List; @@ -49,9 +50,10 @@ public class JClass extends ProjectFile implements JsonSerializable { private Set annotations; /** - * Set of method invocations made from within this class + * List of method invocations made from within this class */ - private Set methodCalls; + private List methodCalls; + public JClass(String name, String path, String packageName, ClassRole classRole) { this.name = name; @@ -61,12 +63,12 @@ public JClass(String name, String path, String packageName, ClassRole classRole) this.methods = new HashSet<>(); this.fields = new HashSet<>(); this.annotations = new HashSet<>(); - this.methodCalls = new HashSet<>(); + this.methodCalls = new ArrayList<>(); this.implementedTypes = new HashSet<>(); this.fileType = FileType.JCLASS; } - public JClass(String name, String path, String packageName, ClassRole classRole, Set methods, Set fields, Set classAnnotations, Set methodCalls, Set implementedTypes) { + public JClass(String name, String path, String packageName, ClassRole classRole, Set methods, Set fields, Set classAnnotations, List methodCalls, Set implementedTypes) { this.name = name; this.packageName = packageName; this.path = path; @@ -118,11 +120,9 @@ public Set getEndpoints() { * see {@link RestCall} * @return set of all restCalls */ - public Set getRestCalls() { - if((!getClassRole().equals(ClassRole.SERVICE) && !getClassRole().equals(ClassRole.FEIGN_CLIENT)) || getMethodCalls().isEmpty()) { - return new HashSet<>(); - } - return methodCalls.stream().filter(methodCall -> methodCall instanceof RestCall).map(methodCall -> (RestCall) methodCall).collect(Collectors.toUnmodifiableSet()); + public List getRestCalls() { + + return methodCalls.stream().filter(methodCall -> methodCall instanceof RestCall).map(methodCall -> (RestCall) methodCall).collect(Collectors.toUnmodifiableList()); } /** diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java index 98518002..cd569a12 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java @@ -1,5 +1,7 @@ package edu.university.ecs.lab.common.models.ir; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.MethodDeclaration; import com.google.gson.JsonObject; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Data; @@ -21,7 +23,7 @@ public class Method extends Node { /** * Set of fields representing parameters */ - protected Set parameters; + protected Set parameters; /** * Java return type of the method @@ -43,7 +45,7 @@ public class Method extends Node { */ protected String className; - public Method(String name, String packageAndClassName, Set parameters, String typeAsString, Set annotations, String microserviceName, + public Method(String name, String packageAndClassName, Set parameters, String typeAsString, Set annotations, String microserviceName, String className) { this.name = name; this.packageAndClassName = packageAndClassName; @@ -54,6 +56,12 @@ public Method(String name, String packageAndClassName, Set parameters, St this.className = className; } + public Method(MethodDeclaration methodDeclaration) { + this.name = methodDeclaration.getNameAsString(); + this.packageAndClassName = methodDeclaration.getClass().getPackageName() + "." + methodDeclaration.getClass().getName(); + this.parameters = parseParameters(methodDeclaration.getParameters()); + } + /** * see {@link JsonSerializable#toJsonObject()} */ @@ -72,4 +80,15 @@ public JsonObject toJsonObject() { return jsonObject; } + private Set parseParameters(NodeList parameters) { + HashSet parameterSet = new HashSet<>(); + + for(com.github.javaparser.ast.body.Parameter parameter : parameters) { + parameterSet.add(new Parameter(parameter, getPackageAndClassName())); + } + + + return parameterSet; + } + } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java index a3f6fd32..d8d98164 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -272,28 +273,22 @@ public Set getAllFiles() { return set; } - public Set getRestCalls () { - Set restCalls = new HashSet<>(); - restCalls.addAll(getServices().stream() - .flatMap(service -> service.getRestCalls().stream()) - .collect(Collectors.toSet())); - restCalls.addAll(getFeignClients().stream() - .flatMap(client -> client.getRestCalls().stream()) - .collect(Collectors.toSet())); - return restCalls; + public List getRestCalls () { + return getClasses().stream() + .flatMap(jClass -> jClass.getRestCalls().stream()).collect(Collectors.toList()); } public Set getEndpoints () { - return this.controllers.stream().flatMap(controller -> + return getControllers().stream().flatMap(controller -> controller.getEndpoints().stream()).collect(Collectors.toSet()); } public Set getMethodCalls () { - return this.getClasses().stream().flatMap(jClass -> jClass.getMethodCalls().stream()).collect(Collectors.toSet()); + return getClasses().stream().flatMap(jClass -> jClass.getMethodCalls().stream()).collect(Collectors.toSet()); } public Set getMethods () { - return this.getClasses().stream().flatMap(jClass -> jClass.getMethods().stream()).collect(Collectors.toSet()); + return getClasses().stream().flatMap(jClass -> jClass.getMethods().stream()).collect(Collectors.toSet()); } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java new file mode 100644 index 00000000..7e2c2b1c --- /dev/null +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java @@ -0,0 +1,49 @@ +package edu.university.ecs.lab.common.models.ir; + +import com.google.gson.JsonObject; +import edu.university.ecs.lab.common.models.serialization.JsonSerializable; +import lombok.Data; + +import java.util.Set; +import java.util.stream.Collectors; + +@Data +public class Parameter extends Node implements JsonSerializable { + + /** + * Java class type of the class variable e.g. String + */ + private String type; + + private Set annotations; + + public Parameter(String name, String packageAndClassName, String type, Set annotations) { + this.name = name; + this.packageAndClassName = packageAndClassName; + this.type = type; + this.annotations = annotations; + } + + public Parameter(com.github.javaparser.ast.body.Parameter parameter, String packageAndClassName) { + this.name = parameter.getNameAsString(); + this.type = parameter.getTypeAsString(); + this.packageAndClassName = packageAndClassName; + this.annotations = parameter.getAnnotations().stream().map(annotationExpr -> new Annotation(annotationExpr, packageAndClassName)).collect(Collectors.toSet()); + } + + + /** + * see {@link JsonSerializable#toJsonObject()} + */ + @Override + public JsonObject toJsonObject() { + JsonObject jsonObject = new JsonObject(); + + jsonObject.addProperty("name", getName()); + jsonObject.addProperty("packageAndClassName", getPackageAndClassName()); + jsonObject.addProperty("type", getType()); + jsonObject.add("annotations", JsonSerializable.toJsonArray(annotations)); + + return jsonObject; + } +} diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java b/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java index 9a800a06..13e0433d 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java @@ -1,11 +1,15 @@ package edu.university.ecs.lab.common.models.ir; +import com.google.gson.Gson; import com.google.gson.JsonObject; import edu.university.ecs.lab.common.models.enums.HttpMethod; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; +import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.Set; + /** * Represents an extension of a method call. A rest call exists at the service level and represents @@ -27,6 +31,8 @@ public class RestCall extends MethodCall { private HttpMethod httpMethod; + + public RestCall(String methodName, String packageAndClassName, String objectType, String objectName, String calledFrom, String parameterContents, String microserviceName, String className) { super(methodName, packageAndClassName, objectType, objectName, calledFrom, parameterContents, @@ -57,6 +63,35 @@ public static boolean matchEndpoint(RestCall restcall, Endpoint endpoint){ return false; } - return restcall.getUrl().equals(endpoint.getUrl()) && restcall.getHttpMethod().equals(endpoint.getHttpMethod()); + int queryParamIndex = restcall.getUrl().replace("{?}", "temp").indexOf("?"); + String baseURL = queryParamIndex == -1 ? restcall.getUrl() : restcall.getUrl().substring(0, queryParamIndex); + return baseURL.equals(endpoint.getUrl()) && (restcall.getHttpMethod().equals(endpoint.getHttpMethod()) || endpoint.getHttpMethod().equals(HttpMethod.ALL)) && matchQueryParams(restcall, endpoint, queryParamIndex); + } + + private static boolean matchQueryParams(RestCall restCall, Endpoint endpoint, int queryParamIndex) { + for(Parameter parameter : endpoint.getParameters()) { + for(Annotation annotation : parameter.getAnnotations()) { + if(annotation.getName().equals("RequestParam")) { + String queryParameterName = ""; + if(annotation.getAttributes().containsKey("default")) { + queryParameterName = annotation.getAttributes().get("default"); + } else if(annotation.getAttributes().containsKey("name")) { + if(annotation.getAttributes().containsKey("required") + && annotation.getAttributes().get("required").equals("false")) { + continue; + } + queryParameterName = annotation.getAttributes().get("name"); + } else { + queryParameterName = parameter.getName(); + } + + if(!restCall.getUrl().substring(queryParamIndex + 1, restCall.getUrl().length()).contains(queryParameterName + "=")) { + return false; + } + } + } + } + return true; } + } diff --git a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java index 7c5b344b..289d1ffc 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java +++ b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java @@ -5,6 +5,8 @@ import edu.university.ecs.lab.common.models.ir.RestCall; import edu.university.ecs.lab.common.models.enums.HttpMethod; import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; /** * Class for deserializing a MethodCall when using Gson @@ -42,6 +44,8 @@ private RestCall jsonToRestCall(JsonObject json) throws JsonParseException { String httpMethod = json.get("httpMethod").getAsString(); + + return new RestCall(methodCall, url, HttpMethod.valueOf(httpMethod)); } } diff --git a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java index b24023bb..be163af8 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java +++ b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java @@ -1,10 +1,7 @@ package edu.university.ecs.lab.common.models.serialization; import com.google.gson.*; -import edu.university.ecs.lab.common.models.ir.Annotation; -import edu.university.ecs.lab.common.models.ir.Endpoint; -import edu.university.ecs.lab.common.models.ir.Field; -import edu.university.ecs.lab.common.models.ir.Method; +import edu.university.ecs.lab.common.models.ir.*; import edu.university.ecs.lab.common.models.enums.HttpMethod; import java.lang.reflect.Type; import java.util.ArrayList; @@ -38,11 +35,11 @@ private Method jsonToMethod(JsonObject json, JsonDeserializationContext context) } method.setAnnotations(annotations); - Set fields = new HashSet(); + Set parameters = new HashSet<>(); for (JsonElement fieldJson : json.get("parameters").getAsJsonArray()) { - fields.add(context.deserialize(fieldJson, Field.class)); + parameters.add(context.deserialize(fieldJson, Parameter.class)); } - method.setParameters(fields); + method.setParameters(parameters); method.setPackageAndClassName(json.get("packageAndClassName").getAsString()); method.setMicroserviceName(json.get("microserviceName").getAsString()); method.setClassName(json.get("className").getAsString()); diff --git a/src/main/java/edu/university/ecs/lab/common/services/GitService.java b/src/main/java/edu/university/ecs/lab/common/services/GitService.java index 1f5ced35..5a712c93 100644 --- a/src/main/java/edu/university/ecs/lab/common/services/GitService.java +++ b/src/main/java/edu/university/ecs/lab/common/services/GitService.java @@ -7,73 +7,37 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.*; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; -/** - * Service for managing local repository including the cloning - * and resetting the current commit. - */ public class GitService { private static final int EXIT_SUCCESS = 0; private static final String HEAD_COMMIT = "HEAD"; - /** - * Configuration file path - */ private final Config config; - - /** - * Repository object for jgit usage - */ private final Repository repository; - - /** - * Instantiation of this service will result in the following - * 1.) output and clone directories will be created or validated - * 2.) Configuration file will be read and validated by it's constructor - * 3.) The repository in config will be cloned or validated - * - * @param configPath - */ public GitService(String configPath) { - // 1.) Read config this.config = ConfigUtil.readConfig(configPath); - - // 2.) Make the output and clone directory FileUtils.makeDirs(); - - // 3.) Clone the repository cloneRemote(); - - // If clone was successful we can now set repo and reset local repo to config base commit this.repository = initRepository(); - } - - /** - * This method clones a remote repository to the local file system. Postcondition: the repository - * has been cloned to the local file system. - * - */ public void cloneRemote() { - // Quietly return assuming cloning already took place String repositoryPath = FileUtils.getRepositoryPath(config.getRepoName()); - // Quietly return assuming cloning already took place if (new File(repositoryPath).exists()) { return; } @@ -83,10 +47,8 @@ public void cloneRemote() { new ProcessBuilder("git", "clone", config.getRepositoryURL(), repositoryPath); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); - int exitCode = process.waitFor(); - if (exitCode != EXIT_SUCCESS) { throw new Exception(); } @@ -98,17 +60,9 @@ public void cloneRemote() { LoggerManager.info(() -> "Cloned repository " + config.getRepoName()); } - /** - * This method resets the local repository to commitID. - * Used to initially set commit for clone and additionally to - * advance the local repository as we step through commits - * - * @param commitID if empty or null, defaults to HEAD - */ public void resetLocal(String commitID) { validateLocalExists(); - // If an invalid commit is passed simply make no change if (Objects.isNull(commitID) || commitID.isEmpty()) { return; } @@ -120,14 +74,8 @@ public void resetLocal(String commitID) { } LoggerManager.info(() -> "Set repository " + config.getRepoName() + " to " + commitID); - - } - /** - * This method validates that the local repository exists or - * reports and exits if it doesn't. - */ private void validateLocalExists() { File file = new File(FileUtils.getRepositoryPath(config.getRepoName())); if (!(file.exists() && file.isDirectory())) { @@ -135,11 +83,6 @@ private void validateLocalExists() { } } - /** - * Establish a local endpoint for the given repository path. - * - * @return the repository object - */ public Repository initRepository() { validateLocalExists(); @@ -150,26 +93,19 @@ public Repository initRepository() { repository = new FileRepositoryBuilder().setGitDir(new File(repositoryPath, ".git")).build(); } catch (Exception e) { - Error.reportAndExit(Error.GIT_FAILED,Optional.of(e)); + Error.reportAndExit(Error.GIT_FAILED, Optional.of(e)); } return repository; } - - /** - * Get the differences between commitOld and commitNew - * - * @param commitOld the old commit ID - * @param commitNew the new commit ID - * @return the list of differences as DiffEntrys - */ public List getDifferences(String commitOld, String commitNew) { List returnList = null; RevCommit oldCommit = null, newCommit = null; RevWalk revWalk = new RevWalk(repository); try { + // Parse the old and new commits oldCommit = revWalk.parseCommit(repository.resolve(commitOld)); newCommit = revWalk.parseCommit(repository.resolve(commitNew)); } catch (Exception e) { @@ -180,16 +116,24 @@ public List getDifferences(String commitOld, String commitNew) { try (ObjectReader reader = repository.newObjectReader()) { CanonicalTreeParser oldTreeParser = new CanonicalTreeParser(); CanonicalTreeParser newTreeParser = new CanonicalTreeParser(); - oldTreeParser.reset(reader, oldCommit.getTree()); - newTreeParser.reset(reader, newCommit.getTree()); - // Compute differences + // Use tree objects from the commits + oldTreeParser.reset(reader, oldCommit.getTree().getId()); + newTreeParser.reset(reader, newCommit.getTree().getId()); + + // Compute differences between the trees of the two commits try (Git git = new Git(repository)) { - returnList = git.diff() - .setNewTree(newTreeParser) + List rawDiffs = git.diff() .setOldTree(oldTreeParser) + .setNewTree(newTreeParser) .call(); + // Filter out diffs that only contain whitespace or comment changes + RevCommit finalOldCommit = oldCommit; + RevCommit finalNewCommit = newCommit; + returnList = rawDiffs.stream() + .filter(diff -> isCodeChange(diff, repository, finalOldCommit, finalNewCommit)) + .collect(Collectors.toList()); } } catch (Exception e) { Error.reportAndExit(Error.GIT_FAILED, Optional.of(e)); @@ -200,6 +144,58 @@ public List getDifferences(String commitOld, String commitNew) { return returnList; } + private boolean isCodeChange(DiffEntry diff, Repository repository, RevCommit oldCommit, RevCommit newCommit) { + if((!diff.getOldPath().endsWith(".java") && !diff.getNewPath().endsWith(".java"))) { + return true; + } + + // Read the file contents before and after the changes + String oldContent = getContentFromTree(repository, oldCommit.getTree().getId(), diff.getOldPath()); + String newContent = getContentFromTree(repository, newCommit.getTree().getId(), diff.getNewPath()); + + // Remove comments and whitespace from both contents + String oldCode = stripCommentsAndWhitespace(oldContent); + String newCode = stripCommentsAndWhitespace(newContent); + + // If the meaningful code is different, return true + return !oldCode.equals(newCode); + } + + private String getContentFromTree(Repository repository, ObjectId treeId, String filePath) { + try (ObjectReader reader = repository.newObjectReader(); + TreeWalk treeWalk = new TreeWalk(repository)) { + + // Add the tree to the tree walker + treeWalk.addTree(treeId); + treeWalk.setRecursive(true); // We want to search recursively + + // Walk through the tree to find the file + while (treeWalk.next()) { + String currentPath = treeWalk.getPathString(); + if (currentPath.equals(filePath)) { + // Ensure we have a blob (file) and not a tree + if (treeWalk.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) { + // Read the file content and return it + byte[] data = reader.open(treeWalk.getObjectId(0)).getBytes(); + return new String(data, StandardCharsets.UTF_8); + } + } + } + + } catch (Exception e) { + // Return an empty string in case of an error + return ""; + } + + // If the file is not found, return an empty string + return ""; + } + + + private String stripCommentsAndWhitespace(String content) { + return content.replaceAll("(//.*|/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/|\\s+)", ""); + } + public Iterable getLog() { Iterable returnList = null; @@ -216,14 +212,10 @@ public String getHeadCommit() { String commitID = ""; try { - // Get the reference to HEAD Ref head = repository.findRef(HEAD_COMMIT); RevWalk walk = new RevWalk(repository); - - // Use RevWalk to parse the commit ObjectId commitId = head.getObjectId(); RevCommit commit = walk.parseCommit(commitId); - commitID = commit.getName(); } catch (Exception e) { @@ -232,6 +224,4 @@ public String getHeadCommit() { return commitID; } - - } diff --git a/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java index acfd44a2..26967660 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java @@ -34,7 +34,7 @@ private JsonReadWriteUtils() { * @param filePath the file path where the JSON should be saved */ public static void writeToJSON(String filePath, T object) { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); + Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); try { Path path = Paths.get(filePath); Files.createDirectories(path.getParent()); diff --git a/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java index b879c9a2..5cd68f21 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.PackageDeclaration; import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.body.Parameter; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName; import com.github.javaparser.resolution.UnsolvedSymbolException; @@ -139,9 +140,9 @@ public static Set parseMethods(List methodDeclaration Set methods = new HashSet<>(); for (MethodDeclaration methodDeclaration : methodDeclarations) { - Set parameters = new HashSet<>(); + Set parameters = new HashSet<>(); for (Parameter parameter : methodDeclaration.getParameters()) { - parameters.add(new Field(parameter.getNameAsString(), packageAndClassName, parameter.getTypeAsString())); + parameters.add(new edu.university.ecs.lab.common.models.ir.Parameter(parameter, packageAndClassName)); } Method method = new Method( @@ -192,8 +193,8 @@ public static Method convertValidEndpoints(MethodDeclaration methodDeclaration, * @param methodDeclarations the list of methodDeclarations to be parsed * @return a set of MethodCall models representing MethodCallExpressions found in the MethodDeclarations */ - public static Set parseMethodCalls(List methodDeclarations) { - Set methodCalls = new HashSet<>(); + public static List parseMethodCalls(List methodDeclarations) { + List methodCalls = new ArrayList<>(); // loop through method calls for (MethodDeclaration methodDeclaration : methodDeclarations) { @@ -231,7 +232,7 @@ public static MethodCall convertValidRestCalls(MethodCallExpr methodCallExpr, Me return methodCall; } - RestCallTemplate restCallTemplate = new RestCallTemplate(methodCallExpr, cu); + RestCallTemplate restCallTemplate = new RestCallTemplate(methodCallExpr,methodCall, cu); if (restCallTemplate.getUrl().isEmpty()) { return methodCall; @@ -316,22 +317,7 @@ private static Set parseAnnotations(Iterable annotat Set annotations = new HashSet<>(); for (AnnotationExpr ae : annotationExprs) { - Annotation annotation; - if (ae.isNormalAnnotationExpr()) { - NormalAnnotationExpr normal = ae.asNormalAnnotationExpr(); - annotation = new Annotation(ae.getNameAsString(), packageAndClassName, normal.getPairs().toString()); - - } else if (ae.isSingleMemberAnnotationExpr()) { - annotation = - new Annotation( - ae.getNameAsString(), - packageAndClassName, - ae.asSingleMemberAnnotationExpr().getMemberValue().toString()); - } else { - annotation = new Annotation(ae.getNameAsString(), packageAndClassName, ""); - } - - annotations.add(annotation); + annotations.add(new Annotation(ae, packageAndClassName)); } return annotations; @@ -393,14 +379,37 @@ private static JClass handleFeignClient(AnnotationExpr requestMapping, Set newMethods = new HashSet<>(); // New rest calls for conversion - Set newRestCalls = new HashSet<>(); + List newRestCalls = new ArrayList<>(); // For each method that is detected as an endpoint convert into a Method + RestCall for(Method method : methods) { if(method instanceof Endpoint) { Endpoint endpoint = (Endpoint) method; newMethods.add(new Method(method.getName(), packageAndClassName, method.getParameters(), method.getReturnType(), method.getAnnotations(), method.getMicroserviceName(), method.getClassName())); - newRestCalls.add(new RestCall(new MethodCall("exchange", packageAndClassName, "RestCallTemplate", "restCallTemplate", method.getName(), "", endpoint.getMicroserviceName(), endpoint.getClassName()), endpoint.getUrl(), endpoint.getHttpMethod())); + + StringBuilder queryParams = new StringBuilder(); + for(edu.university.ecs.lab.common.models.ir.Parameter parameter : method.getParameters()) { + for(Annotation annotation : parameter.getAnnotations()) { + if(annotation.getName().equals("RequestParam")) { + queryParams.append("&"); + if(annotation.getAttributes().containsKey("default")) { + queryParams.append(annotation.getAttributes().get("default")); + } else if(annotation.getAttributes().containsKey("name")) { + queryParams.append(annotation.getAttributes().get("name")); + } else { + queryParams.append(parameter.getName()); + } + + queryParams.append("={?}"); + } + } + } + + if(!queryParams.isEmpty()) { + queryParams.replace(0, 1, "?"); + } + + newRestCalls.add(new RestCall(new MethodCall("exchange", packageAndClassName, "RestCallTemplate", "restCallTemplate", method.getName(), "", endpoint.getMicroserviceName(), endpoint.getClassName()), endpoint.getUrl() + queryParams, endpoint.getHttpMethod())); } else { newMethods.add(method); } @@ -464,7 +473,7 @@ private static JClass handleRepositoryRestResource(AnnotationExpr requestMapping // New methods for conversion Set newEndpoints = new HashSet<>(); // New rest calls for conversion - Set newRestCalls = new HashSet<>(); + List newRestCalls = new ArrayList<>(); // Arbitrary preURL naming scheme if not defined in the annotation String preURL = "/" + className.toLowerCase().replace("repository", "") + "s"; @@ -537,7 +546,7 @@ private static JClass handleRepositoryRestResource(AnnotationExpr requestMapping } private static JClass handleJS(String filePath) { - JClass jClass = new JClass(filePath, filePath, "", ClassRole.FEIGN_CLIENT, new HashSet<>(), new HashSet<>(), new HashSet<>(), new HashSet<>(), new HashSet<>()); + JClass jClass = new JClass(filePath, filePath, "", ClassRole.FEIGN_CLIENT, new HashSet<>(), new HashSet<>(), new HashSet<>(), new ArrayList<>(), new HashSet<>()); try { Set restCalls = new HashSet<>(); // Command to run Node.js script diff --git a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java index 92a9d434..576078f8 100644 --- a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java @@ -46,7 +46,7 @@ public class DetectionService { "Wrong Cuts", "Cyclic Dependencies (MS level)", "Cyclic Dependencies (Method level)", "Wobbly Service Interactions", "No Healthchecks", "No API Gateway", "maxAIS", "avgAIS", "stdAIS", "maxADS", "ADCS", "stdADS", "maxACS", "avgACS", "stdACS", "SCF", "SIY", "maxSC", "avgSC", "stdSC", "SCCmodularity", "maxSIDC", "avgSIDC", "stdSIDC", "maxSSIC", "avgSSIC", "stdSSIC", - "maxLOMLC", "avgLOMLC", "stdLOMLC", "AR3 (System)","AR4 (System)", "AR6 (Delta)", "AR20 (System)"}; + "maxLOMLC", "avgLOMLC", "stdLOMLC", "AR3 (System)","AR4 (System)", "AR6 (Change)", "AR7 (Change)"}; /** * Count of antipatterns, metrics, and architectural rules @@ -55,8 +55,8 @@ public class DetectionService { private static final int METRICS = 24; private static final int ARCHRULES = 4; - private static final String BASE_DELTA_PATH = "./output/Delta/Delta"; - private static final String BASE_IR_PATH = "./output/IR/IR"; + private static String BASE_DELTA_PATH = "/Delta/Delta"; + private static String BASE_IR_PATH = "/IR/IR"; @@ -87,6 +87,9 @@ public DetectionService(String configPath) { // Setup local repo gitService = new GitService(configPath); workbook = new XSSFWorkbook(); + + BASE_DELTA_PATH = "./output/" + config.getRepoName() + BASE_DELTA_PATH; + BASE_IR_PATH = "./output/" + config.getRepoName() + BASE_IR_PATH; } /** @@ -188,7 +191,7 @@ public void runDetection() { } // At the end we write the workbook to file - try (FileOutputStream fileOut = new FileOutputStream(String.format("./output/AntiPatterns_%s.xlsx", config.getSystemName()))) { + try (FileOutputStream fileOut = new FileOutputStream(String.format("./output/%s/output-%s.xlsx",config.getRepoName(), config.getSystemName()))) { workbook.write(fileOut); System.out.printf("Excel file created: AntiPatterns_%s.xlsx%n", config.getSystemName()); workbook.close(); @@ -327,7 +330,7 @@ private void updateRules(int rowIndex, List currARs) { arcrules_counts[1]++; } else if (archRule instanceof AR6) { arcrules_counts[2]++; - } else if (archRule instanceof AR20) { + } else if (archRule instanceof AR7) { arcrules_counts[3]++; } } diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AntiPattern.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AbstractAntiPattern.java similarity index 91% rename from src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AntiPattern.java rename to src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AbstractAntiPattern.java index 9d5a27f0..424740e3 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AntiPattern.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AbstractAntiPattern.java @@ -7,7 +7,7 @@ * Abstract implementation of an Antipattern should be the parent * of all system Antipatterns */ -public abstract class AntiPattern implements JsonSerializable { +public abstract class AbstractAntiPattern implements JsonSerializable { protected abstract String getName(); protected abstract String getDescription(); protected abstract JsonObject getMetaData(); diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java index 8324739b..6c0c76c5 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java @@ -13,7 +13,7 @@ * Represents a list of one cycle of Cyclic Dependency Anti-pattern detected */ @Data -public class CyclicDependency extends AntiPattern{ +public class CyclicDependency extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java index 565a55b6..106be674 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java @@ -11,7 +11,7 @@ * Represents a collection of microservices identified as greedy. */ @Data -public class GreedyMicroservice extends AntiPattern { +public class GreedyMicroservice extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java index 8affba78..92d5b404 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java @@ -10,7 +10,7 @@ * Represents a collection of microservices identified as hub-like. */ @Data -public class HubLikeMicroservice extends AntiPattern { +public class HubLikeMicroservice extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java index 1baae595..6b401240 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java @@ -8,7 +8,7 @@ * Represents the "No API-Gateway" anti-pattern */ @Data -public class NoApiGateway extends AntiPattern{ +public class NoApiGateway extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java index 4604e6da..60956a05 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java @@ -11,7 +11,7 @@ * Represents the "No Health Check" anti-pattern */ @Data -public class NoHealthcheck extends AntiPattern{ +public class NoHealthcheck extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java index 8817b7ba..7295b973 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java @@ -11,7 +11,7 @@ * Represents a service chain, which is a sequence of services in a network graph. */ @Data -public class ServiceChain extends AntiPattern{ +public class ServiceChain extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java index a00e2c31..dbdc7c15 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java @@ -11,7 +11,7 @@ * Represents a wobbly service interaction. */ @Data -public class WobblyServiceInteraction extends AntiPattern { +public class WobblyServiceInteraction extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java index 880bd60a..e82819c0 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java @@ -11,7 +11,7 @@ * Represents a cluster of wrongly interconnected services (Wrong Cuts) detected in a microservice network graph. */ @Data -public class WrongCuts extends AntiPattern{ +public class WrongCuts extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java index 7793c783..19a0cbd7 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java @@ -11,7 +11,7 @@ */ public class CyclicDependencyMSLevelService { - private List> allCycles = new ArrayList<>(); // + private List> allCycles = new ArrayList<>(); private Set visited = new HashSet<>(); private Set recStack = new HashSet<>(); private Map parentMap = new HashMap<>(); @@ -73,15 +73,18 @@ private List reconstructCyclePath(Microservice startNode, Microservice c List fullCyclePath = new ArrayList<>(); Microservice node = currentNode; - // Iterate through each node in parent map until startNode is reached, adding each to + // Iterate through each node in parent map until startNode is reached, adding each to // the cycle path - fullCyclePath.add(startNode.getName()); while (node != null && !node.equals(startNode)) { fullCyclePath.add(node.getName()); node = parentMap.get(node); } + + // Only add startNode at the end to complete the cycle fullCyclePath.add(startNode.getName()); + Collections.reverse(fullCyclePath); // Reverse the path to get correct order + return fullCyclePath; } } diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java index 2e30b9f3..97762cfe 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java @@ -33,6 +33,7 @@ public WobblyServiceInteraction findWobblyServiceInteractions(MicroserviceSystem boolean hasRateLimiter = false; boolean hasRetry = false; boolean hasBulkhead = false; + boolean passes = false; // Check annotations at the method level for (Method method : jClass.getMethods()) { diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java index de3c892f..2948a596 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java @@ -16,7 +16,7 @@ import lombok.Data; /** - * Architectural Rule 4 Class: Floating endpoint due to last call removal + * Architectural Rule 4 Class: Floating endpoint */ @Data public class AR4 extends AbstractAR { @@ -25,8 +25,8 @@ public class AR4 extends AbstractAR { * Architectural rule 4 details */ protected static final String TYPE = "Architectural Rule 4"; - protected static final String NAME = "Floating endpoint due to last call removal"; - protected static final String DESC = "Any rest calls referencing an endpoint are now gone. This endpoint is now unused by any other microservice"; + protected static final String NAME = "Floating endpoint"; + protected static final String DESC = "No rest calls reference this endpoint. This endpoint is now unused by any other microservice"; private String oldCommitID; private String newCommitID; @@ -73,7 +73,7 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser // Get endpoints that do not have any calls Set uncalledEndpoints = getEndpointsWithNoCalls(newSystem); - Set restCalls = new HashSet<>(); + List restCalls = new ArrayList<>(); if (delta.getChangeType().equals(ChangeType.MODIFY)) { restCalls = getRemovedRestCalls(delta, jClass); @@ -123,8 +123,8 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser * @param oldClass the delta changed class from the oldSystem * @return a set of rest calls */ - private static Set getRemovedRestCalls(Delta delta, JClass oldClass){ - Set removedRestCalls = new HashSet<>(); + private static List getRemovedRestCalls(Delta delta, JClass oldClass){ + List removedRestCalls = new ArrayList<>(); // For the restCalls in delta for(RestCall modifiedRestCall: delta.getClassChange().getRestCalls()){ diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java index 51790cbc..3b5aca78 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java @@ -9,11 +9,9 @@ import edu.university.ecs.lab.delta.models.Delta; import edu.university.ecs.lab.delta.models.enums.ChangeType; import lombok.Data; +import org.apache.poi.ss.formula.functions.T; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -78,21 +76,21 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser return new ArrayList<>(); } + List uniqueMethodCalls = getUniqueMethodCalls(jClass.getMethodCalls(), delta.getClassChange().getMethodCalls()); + List archRules = new ArrayList<>(); - Set affectedMethods = delta.getClassChange().getMethods(); - Method removeMethod = null; // For each methodCall added - for(MethodCall methodCall : getNewMethodCalls(jClass, delta.getClassChange())) { + for(Method method : delta.getClassChange().getMethods()) { outer: { - for (Method method : affectedMethods) { + + for (MethodCall methodCall : uniqueMethodCalls) { // If the called object is the same as the return type of the same method // TODO This will report the first instance // TODO This currently does not check if the affected method is called by an endpoint and actually used, flows needed if (method.getReturnType().equals(methodCall.getObjectType()) && method.getName().equals(methodCall.getCalledFrom())) { - removeMethod = method; AR6 archRule6 = new AR6(); JsonObject jsonObject = new JsonObject(); @@ -110,23 +108,60 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser } } - if(Objects.nonNull(removeMethod)) { - affectedMethods.remove(removeMethod); - removeMethod = null; - } } return archRules; } - /** - * This method gets new method calls not present in oldClass - * - * @param oldClass old commit class - * @param newClass delta class change - * @return list of new method calls (not present in old class) - */ - private static Set getNewMethodCalls(JClass oldClass, JClass newClass) { - return newClass.getMethodCalls().stream().filter(methodCall -> !oldClass.getMethodCalls().contains(methodCall)).collect(Collectors.toSet()); + // Define a custom comparator for the MethodCall class + private static Comparator methodCallComparator = Comparator + .comparing(MethodCall::getName) + .thenComparing(MethodCall::getParameterContents) + .thenComparing(MethodCall::getObjectType) + .thenComparing(MethodCall::getCalledFrom); + + public static List getUniqueMethodCalls(List oldMethodCalls, List newMethodCalls) { + // Using TreeSet with a custom comparator to enforce uniqueness based on custom logic + List final1 = new ArrayList<>(); + + + boolean foundMatch = false; + for(MethodCall oldmethodCall : oldMethodCalls) { + outer: + { + for (MethodCall newMethodCall : newMethodCalls) { + if (methodCallComparator.compare(oldmethodCall, newMethodCall) == 0) { + foundMatch = true; + break outer; + } + } + + if(!foundMatch) { + final1.add(oldmethodCall); + } + foundMatch = false; + } + } + + for(MethodCall newMethodCall : newMethodCalls) { + outer: + { + for (MethodCall oldmethodCall : oldMethodCalls) { + if (methodCallComparator.compare(oldmethodCall, newMethodCall) == 0) { + foundMatch = true; + break outer; + } + } + + if(!foundMatch) { + final1.add(newMethodCall); + } + foundMatch = false; + } + } + + + return final1; } + } diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java index 0ad78d50..5ab6ddee 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java @@ -1,7 +1,10 @@ package edu.university.ecs.lab.detection.architecture.models; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import com.google.gson.JsonObject; @@ -63,32 +66,68 @@ public String getType() { public static List scan(Delta delta, MicroserviceSystem oldSystem, MicroserviceSystem newSystem) { List useCases = new ArrayList<>(); - JClass oldClass = oldSystem.findClass(delta.getOldPath()); + JClass jClass = oldSystem.findClass(delta.getOldPath()); + if(jClass == null) { + return new ArrayList<>(); + } // Return empty list if it isn't modify or not a repository - if (!delta.getChangeType().equals(ChangeType.MODIFY) || !oldClass.getClassRole().equals(ClassRole.REPOSITORY)) { + if (!delta.getChangeType().equals(ChangeType.MODIFY) || !jClass.getClassRole().equals(ClassRole.REPOSITORY)) { return useCases; } - for (Method methodOld : oldClass.getMethods()) { - for (Method methodNew : delta.getClassChange().getMethods()) { - // Match old and new Methods - if (methodOld.getID().equals(methodNew.getID()) && !methodOld.equals(methodNew)) { - // Any annotations that don't equal their counterpart we can add to metadata - for (Annotation annotationOld : methodOld.getAnnotations()) { - for (Annotation annotationNew : methodNew.getAnnotations()) { - // Annotation names match but not the contents - if (annotationNew.getName().equals(annotationOld.getName()) && !annotationNew.getContents().equals(annotationOld.getContents())) { - AR7 useCase7 = new AR7(); - JsonObject jsonObject = new JsonObject(); - jsonObject.isJsonNull(); - jsonObject.addProperty("OldMethodDeclaration", methodOld.getID()); - jsonObject.addProperty("NewMethodDeclaration", methodNew.getID()); - useCase7.setOldCommitID(oldSystem.getCommitID()); - useCase7.setNewCommitID(newSystem.getCommitID()); - - useCase7.setMetaData(jsonObject); - useCases.add(useCase7); + + for (Method methodOld : jClass.getMethods()) { + outer: + { + for (Method methodNew : delta.getClassChange().getMethods()) { + // Match old and new Methods + if (methodOld.getID().equals(methodNew.getID())) { + // Flag any added, removed + Set oldAnnotations = methodOld.getAnnotations().stream() + .map(Annotation::getName) + .collect(Collectors.toSet()); + Set newAnnotations = methodNew.getAnnotations().stream() + .map(Annotation::getName) + .collect(Collectors.toSet()); + + // Check for added or removed annotations + Set addedAnnotations = new HashSet<>(newAnnotations); + addedAnnotations.removeAll(oldAnnotations); + + Set removedAnnotations = new HashSet<>(oldAnnotations); + removedAnnotations.removeAll(newAnnotations); + + if (!addedAnnotations.isEmpty() || !removedAnnotations.isEmpty()) { + AR7 useCase7 = new AR7(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("OldMethodDeclaration", methodOld.getID()); + jsonObject.addProperty("NewMethodDeclaration", methodNew.getID()); + jsonObject.addProperty("AddedAnnotations", addedAnnotations.toString()); + jsonObject.addProperty("RemovedAnnotations", removedAnnotations.toString()); + useCase7.setOldCommitID(oldSystem.getCommitID()); + useCase7.setNewCommitID(newSystem.getCommitID()); + useCase7.setMetaData(jsonObject); + useCases.add(useCase7); + break outer; + } + + // Check for modified annotations (same name but different contents) + for (Annotation annotationOld : methodOld.getAnnotations()) { + for (Annotation annotationNew : methodNew.getAnnotations()) { + if (annotationNew.getName().equals(annotationOld.getName()) + && !annotationNew.getContents().equals(annotationOld.getContents())) { + AR7 useCase7 = new AR7(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("OldMethodDeclaration", methodOld.getID()); + jsonObject.addProperty("NewMethodDeclaration", methodNew.getID()); + jsonObject.addProperty("ModifiedAnnotation", annotationNew.getName()); + useCase7.setOldCommitID(oldSystem.getCommitID()); + useCase7.setNewCommitID(newSystem.getCommitID()); + useCase7.setMetaData(jsonObject); + useCases.add(useCase7); + break outer; + } } } } diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java b/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java index b4179226..4b385b7b 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java @@ -82,10 +82,10 @@ public List scanDeltaUC() { } - // List useCase7List = UseCase7.scan(d, microserviceSystemOld, microserviceSystemNew); - // if(!useCase7List.isEmpty()){ - // useCases.addAll(useCase7List); - // } + List useCase7List = AR7.scan(d, microserviceSystemOld, microserviceSystemNew); + if(!useCase7List.isEmpty()){ + useCases.addAll(useCase7List); + } } return useCases; @@ -109,10 +109,10 @@ public List scanSystemUC() { useCases.addAll(useCase4List); } - List useCase20List = AR20.scan(microserviceSystemOld, microserviceSystemNew); - if(!useCase20List.isEmpty()){ - useCases.addAll(useCase20List); - } +// List useCase7List = AR7.scan(demicroserviceSystemOld, microserviceSystemNew); +// if(!useCase7List.isEmpty()){ +// useCases.addAll(useCase7List); +// } // List useCase21List = UseCase21.scan(microserviceSystemOld, microserviceSystemNew); // if(!useCase21List.isEmpty()){ diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java b/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java index 7ab67811..d69feedf 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java @@ -37,41 +37,30 @@ public static MetricResultCalculation calculateCohesionMetrics(String IRPath) { for (Microservice microservice : microserviceSystem.getMicroservices()) { IServiceDescriptor serviceDescriptor = new ServiceDescriptor(); -// JSONObject microservice = microservices.getJSONObject(i); -// String serviceName = microservice.getString("name"); -// JSONArray controllers = microservice.getJSONArray("controllers"); serviceDescriptor.setServiceName(microservice.getName()); for (JClass controller : microservice.getControllers()) { -// JSONObject controller = controllers.getJSONObject(j); -// JSONArray methods = controller.getJSONArray("methods"); List operations = new ArrayList<>(); for (Method method : controller.getMethods()) { -// JSONObject method = methods.getJSONObject(k); Operation operation = new Operation(); String operationName = microservice.getName() + "::" + method.getName(); operation.setResponseType(method.getReturnType()); operation.setName(operationName); -// JSONArray parameters = method.getJSONArray("parameters"); - List paramList = new ArrayList<>(); - for (Field field : method.getParameters()) { -// JSONObject parameter = parameters.getJSONObject(l); - Parameter param = new Parameter(); - param.setName(field.getName()); - param.setType(field.getType()); - paramList.add(param); + List paramList = new ArrayList<>(); + for (edu.university.ecs.lab.common.models.ir.Parameter field : method.getParameters()) { +// Parameter param = new Parameter(); +// param.setName(field.getName()); +// param.setType(field.getType()); + paramList.add(field.getType()); } operation.setParamList(paramList); List usingTypes = new ArrayList<>(); - // Assume annotations can imply using types -// JSONArray annotations = method.getJSONArray("annotations"); for (Annotation annotation : method.getAnnotations()) { -// JSONObject annotation = annotations.getJSONObject(m); usingTypes.add(annotation.getName() + " - " + annotation.getContents()); } operation.setUsingTypesList(usingTypes); diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java index 395412f6..ed1e6039 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java @@ -98,7 +98,7 @@ public DegreeCoupling(ServiceDependencyGraph graph){ // Service coupling factor (graph density) Map> adjacency = graph.getAdjacency(); double E = adjacency.values().stream().map(Set::size).mapToDouble(Integer::doubleValue).sum(); - long N = AIS.size(); + long N = graph.vertexSet().size(); SCF = E/(N*(N-1)); // Service Interdependence in the System diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java index 86dc2554..9d277146 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java @@ -9,7 +9,7 @@ public class Operation { private String path; private String name; - private List paramList; + private List paramList; private List usingTypesList; private String responseType; @@ -37,11 +37,11 @@ public void setName(String name) { this.name = name; } - public List getParamList() { + public List getParamList() { return paramList; } - public void setParamList(List paramList) { + public void setParamList(List paramList) { this.paramList = paramList; } diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java index 221b5fcd..82f9c67c 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java @@ -46,7 +46,7 @@ public class StructuralCoupling { */ private final double stdGWF; /** - * Structural Coupling(s1, s2) = 1 - 1/degree(s1, s2) - LWF(s1, s2)*GWF(s1, s2) + * Structural Coupling(s1, s2) = 1 - 1/degree(s1, s2) - LWF(s1, s2) * GWF(s1, s2) */ private final Map, Double> SC; /** @@ -114,7 +114,7 @@ public StructuralCoupling(ServiceDependencyGraph graph) { } for (List pair: LWF.keySet()) { GWF.put(pair, degree.get(pair)/max_degree); - SC.put(pair, 1-(1/degree.get(pair))*LWF.get(pair)*GWF.get(pair)); + SC.put(pair, (1-(1/degree.get(pair)))-LWF.get(pair)*GWF.get(pair)); } // Amount of actually connected pairs diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java b/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java index 9abefd53..662b4740 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java @@ -86,8 +86,8 @@ public Double inputDataSimilarity(Operation firstOperation, Operation secondOper HashSet unionOfProperties = new HashSet<>(); // Get parameter names from each operation - List firstOperationParameterNames = Parameter.getParameterNames(firstOperation.getParamList()); - List secondOperationParameterNames = Parameter.getParameterNames(secondOperation.getParamList()); + List firstOperationParameterNames = firstOperation.getParamList(); + List secondOperationParameterNames = secondOperation.getParamList(); // Join properties of both operations and get total number of properties unionOfProperties.addAll(firstOperationParameterNames); diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java b/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java index 83877427..faa583c5 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java @@ -50,7 +50,6 @@ public void evaluate() { long totalOfParamsType = serviceDescriptor.getServiceOperations() .stream() .map(o -> o.getParamList()) - .map(o -> Parameter.getParameterTypes(o)) .filter(Objects::nonNull) .flatMap(types -> types.stream()) .distinct() @@ -70,8 +69,8 @@ public void evaluate() { } private List commonParameterTypes(Operation operation1, Operation operation2) { - List typesIntoFirstOperation = Parameter.getParameterTypes(operation1.getParamList()); - List typesIntoSecondOperation = Parameter.getParameterTypes(operation2.getParamList()); + List typesIntoFirstOperation = operation1.getParamList(); + List typesIntoSecondOperation = operation2.getParamList(); return typesIntoFirstOperation.stream() .filter(typesIntoSecondOperation::contains).collect(Collectors.toList()); diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java b/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java index ba1f5d7e..4bfea4d9 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java @@ -70,9 +70,7 @@ public void evaluate() { }).flatMap(types -> types.stream()) .collect(Collectors.toSet()).size(); - // multiplica por dois porque Ă© o conjunto de pares, entĂŁo tanto a ida como a - // volta - // ex: (Service::A-Service::B e Service::B-Service::A) contam. + this.getResult().setMetricValue(intersectTypesSize * 2.0 / (uniqueUsingTypes.size() * numberOfOperations)); } diff --git a/src/test/java/integration/IRComparisonTest.java b/src/test/java/integration/IRComparisonTest.java index b8345fec..daef48bf 100644 --- a/src/test/java/integration/IRComparisonTest.java +++ b/src/test/java/integration/IRComparisonTest.java @@ -1,128 +1,128 @@ -//package integration; -// -//import edu.university.ecs.lab.common.models.ir.*; -//import edu.university.ecs.lab.common.services.GitService; -//import edu.university.ecs.lab.common.utils.FileUtils; -//import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; -//import edu.university.ecs.lab.delta.services.DeltaExtractionService; -//import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; -//import edu.university.ecs.lab.intermediate.merge.services.MergeService; -//import org.eclipse.jgit.revwalk.RevCommit; -//import org.junit.jupiter.api.Assertions; -//import org.junit.jupiter.api.BeforeAll; -//import org.junit.jupiter.api.Test; -// -//import java.io.IOException; -//import java.nio.file.Files; -//import java.nio.file.Paths; -//import java.nio.file.StandardCopyOption; -//import java.util.*; -// -//import static integration.Constants.*; -// -//class IRComparisonTest { -// private static IRExtractionService irExtractionService; -// private static DeltaExtractionService deltaExtractionService; -// private static List list; -// private static GitService gitService; -// -// -// @BeforeAll -// public static void setUp() { -// FileUtils.makeDirs(); -// gitService = new GitService(TEST_CONFIG_PATH); -// -// list = iterableToList(gitService.getLog()); -// -// irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(0).toString().split(" ")[1])); -// -// irExtractionService.generateIR(OLD_IR_NAME); -// } -// -// @Test -// void testComparison() { -// -// // Loop through commit history and create delta, merge, etc... -// for (int i = 0; i < list.size() - 1; i++) { -// String commitIdOld = list.get(i).toString().split(" ")[1]; -// String commitIdNew = list.get(i + 1).toString().split(" ")[1]; -// -// // Extract changes from one commit to the other -// deltaExtractionService = new DeltaExtractionService(TEST_CONFIG_PATH, OLD_IR_PATH, commitIdOld, commitIdNew); -// deltaExtractionService.generateDelta(); -// -// // Merge Delta changes to old IR to create new IR representing new commit changes -// MergeService mergeService = new MergeService(OLD_IR_PATH, DELTA_PATH, TEST_CONFIG_PATH); -// mergeService.generateMergeIR(commitIdNew); -// -// if(i < list.size() - 2) { -// try { -// Files.move(Paths.get(NEW_IR_PATH), Paths.get(OLD_IR_PATH), StandardCopyOption.REPLACE_EXISTING); -// } catch (IOException e) { -// e.printStackTrace(); +package integration; + +import edu.university.ecs.lab.common.models.ir.*; +import edu.university.ecs.lab.common.services.GitService; +import edu.university.ecs.lab.common.utils.FileUtils; +import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; +import edu.university.ecs.lab.delta.services.DeltaExtractionService; +import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; +import edu.university.ecs.lab.intermediate.merge.services.MergeService; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.*; + +import static integration.Constants.*; + +class IRComparisonTest { + private static IRExtractionService irExtractionService; + private static DeltaExtractionService deltaExtractionService; + private static List list; + private static GitService gitService; + + + @BeforeAll + public static void setUp() { + FileUtils.makeDirs(); + gitService = new GitService(TEST_CONFIG_PATH); + + list = iterableToList(gitService.getLog()); + + irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(0).toString().split(" ")[1])); + + irExtractionService.generateIR("./output/OldIR.json"); + } + + @Test + void testComparison() { + + // Loop through commit history and create delta, merge, etc... + for (int i = 0; i < list.size() - 1; i++) { + String commitIdOld = list.get(i).toString().split(" ")[1]; + String commitIdNew = list.get(i + 1).toString().split(" ")[1]; + + // Extract changes from one commit to the other + deltaExtractionService = new DeltaExtractionService(TEST_CONFIG_PATH, "./output/Delta.json", commitIdOld, commitIdNew); + deltaExtractionService.generateDelta(); + + // Merge Delta changes to old IR to create new IR representing new commit changes + MergeService mergeService = new MergeService("./output/OldIR.json", "./output/Delta.json", TEST_CONFIG_PATH, "./output/NewIR.json"); + mergeService.generateMergeIR(commitIdNew); + + if(i < list.size() - 2) { + try { + Files.move(Paths.get("./output/NewIR.json"), Paths.get("./output/OldIR.json"), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // Create IR of last commit + irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(list.size() - 1).toString().split(" ")[1])); + irExtractionService.generateIR("./output/TestIR.json"); + + // Compare two IR's for equivalence + MicroserviceSystem microserviceSystem1 = JsonReadWriteUtils.readFromJSON("./output/NewIR.json", MicroserviceSystem.class); + MicroserviceSystem microserviceSystem2 = JsonReadWriteUtils.readFromJSON("./output/TestIR.json", MicroserviceSystem.class); + + microserviceSystem1.setOrphans(new HashSet<>()); + microserviceSystem2.setOrphans(new HashSet<>()); + + Assertions.assertTrue(Objects.deepEquals(microserviceSystem1, microserviceSystem2)); + + } + + +// private static void deepCompareSystems(MicroserviceSystem microserviceSystem1, MicroserviceSystem microserviceSystem2) { +// // Ignore orphans for testing +// microserviceSystem1.setOrphans(null); +// microserviceSystem2.setOrphans(null); +// System.out.println("System equivalence is: " + Objects.deepEquals(microserviceSystem1, microserviceSystem2)); +// +// for (Microservice microservice1 : microserviceSystem1.getMicroservices()) { +// outer2: { +// for (Microservice microservice2 : microserviceSystem2.getMicroservices()) { +// if (microservice1.getName().equals(microservice2.getName())) { +// System.out.println("Microservice equivalence of " + microservice1.getPath() + " is: " + Objects.deepEquals(microservice1, microservice2)); +// for (ProjectFile projectFile1 : microservice1.getAllFiles()) { +// outer1: { +// for (ProjectFile projectFile2 : microservice2.getAllFiles()) { +// if (projectFile1.getPath().equals(projectFile2.getPath())) { +// System.out.println("Class equivalence of " + projectFile1.getPath() + " is: " + Objects.deepEquals(projectFile1, projectFile2)); +// break outer1; +// } +// } +// +// System.out.println("No JClass match found for " + projectFile1.getPath()); +// } +// } +// break outer2; +// } // } -// } -// } -// -// // Create IR of last commit -// irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(list.size() - 1).toString().split(" ")[1])); -// irExtractionService.generateIR(TEST_IR_NAME); -// -// // Compare two IR's for equivalence -// MicroserviceSystem microserviceSystem1 = JsonReadWriteUtils.readFromJSON(NEW_IR_PATH, MicroserviceSystem.class); -// MicroserviceSystem microserviceSystem2 = JsonReadWriteUtils.readFromJSON(TEST_IR_PATH, MicroserviceSystem.class); -// -// microserviceSystem1.setOrphans(new HashSet<>()); -// microserviceSystem2.setOrphans(new HashSet<>()); -// -// Assertions.assertTrue(Objects.deepEquals(microserviceSystem1, microserviceSystem2)); // -// } -// -// -//// private static void deepCompareSystems(MicroserviceSystem microserviceSystem1, MicroserviceSystem microserviceSystem2) { -//// // Ignore orphans for testing -//// microserviceSystem1.setOrphans(null); -//// microserviceSystem2.setOrphans(null); -//// System.out.println("System equivalence is: " + Objects.deepEquals(microserviceSystem1, microserviceSystem2)); -//// -//// for (Microservice microservice1 : microserviceSystem1.getMicroservices()) { -//// outer2: { -//// for (Microservice microservice2 : microserviceSystem2.getMicroservices()) { -//// if (microservice1.getName().equals(microservice2.getName())) { -//// System.out.println("Microservice equivalence of " + microservice1.getPath() + " is: " + Objects.deepEquals(microservice1, microservice2)); -//// for (ProjectFile projectFile1 : microservice1.getAllFiles()) { -//// outer1: { -//// for (ProjectFile projectFile2 : microservice2.getAllFiles()) { -//// if (projectFile1.getPath().equals(projectFile2.getPath())) { -//// System.out.println("Class equivalence of " + projectFile1.getPath() + " is: " + Objects.deepEquals(projectFile1, projectFile2)); -//// break outer1; -//// } -//// } -//// -//// System.out.println("No JClass match found for " + projectFile1.getPath()); -//// } -//// } -//// break outer2; -//// } -//// } -//// -//// System.out.println("No Microservice match found for " + microservice1.getPath()); -//// } -//// } -//// -//// } -// -// private static List iterableToList(Iterable iterable) { -// Iterator iterator = iterable.iterator(); -// List list = new LinkedList<>(); -// while (iterator.hasNext()) { -// list.add(iterator.next()); +// System.out.println("No Microservice match found for " + microservice1.getPath()); +// } // } -// Collections.reverse(list); // -// return list; // } -// -// -//} + + private static List iterableToList(Iterable iterable) { + Iterator iterator = iterable.iterator(); + List list = new LinkedList<>(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + Collections.reverse(list); + + return list; + } + + +} diff --git a/src/test/java/unit/antipatterns/CyclicDependencyTest.java b/src/test/java/unit/antipatterns/CyclicDependencyTest.java index 7183361f..660fb200 100644 --- a/src/test/java/unit/antipatterns/CyclicDependencyTest.java +++ b/src/test/java/unit/antipatterns/CyclicDependencyTest.java @@ -59,12 +59,12 @@ public void cyclicDependencyHasNOne() { jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); microservice1.addJClass(jClass1); JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); - jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); microservice1.addJClass(jClass5); Microservice microservice2 = new Microservice("ms2", "/ms2"); JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); - jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); microservice2.addJClass(jClass2); JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); @@ -72,7 +72,7 @@ public void cyclicDependencyHasNOne() { Microservice microservice3 = new Microservice("ms3", "/ms3"); JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); - jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); microservice3.addJClass(jClass4); @@ -92,12 +92,12 @@ public void cyclicDependencyHasOne() { jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); microservice1.addJClass(jClass1); JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); - jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); microservice1.addJClass(jClass5); Microservice microservice2 = new Microservice("ms2", "/ms2"); JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); - jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); microservice2.addJClass(jClass2); JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); @@ -105,7 +105,7 @@ public void cyclicDependencyHasOne() { Microservice microservice3 = new Microservice("ms3", "/ms3"); JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); - jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); microservice3.addJClass(jClass4); JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); @@ -130,12 +130,12 @@ public void cyclicDependencyHasOneAlso() { jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); microservice1.addJClass(jClass1); JClass jClass2 = new JClass("class5", "/class2","class5", ClassRole.SERVICE); - jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); microservice1.addJClass(jClass2); Microservice microservice2 = new Microservice("ms2", "/ms2"); JClass jClass3 = new JClass("class2", "/class3","class2", ClassRole.SERVICE); - jClass3.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + jClass3.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); microservice2.addJClass(jClass3); JClass jClass4 = new JClass("class3", "/class4","class3", ClassRole.CONTROLLER); jClass4.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); @@ -162,12 +162,12 @@ public void cyclicDependencyHasTwo() { jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); microservice1.addJClass(jClass1); JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); - jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); microservice1.addJClass(jClass5); Microservice microservice2 = new Microservice("ms2", "/ms2"); JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); - jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); microservice2.addJClass(jClass2); JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); @@ -175,10 +175,10 @@ public void cyclicDependencyHasTwo() { Microservice microservice3 = new Microservice("ms3", "/ms3"); JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); - jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); microservice3.addJClass(jClass4); JClass jClass9 = new JClass("class9", "/class9","class9", ClassRole.SERVICE); - jClass9.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint4", HttpMethod.GET))); + jClass9.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint4", HttpMethod.GET))); microservice3.addJClass(jClass9); JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); @@ -186,7 +186,7 @@ public void cyclicDependencyHasTwo() { Microservice microservice4 = new Microservice("ms4", "/ms4"); JClass jClass7 = new JClass("jClass7", "/jClass7","jClass7", ClassRole.SERVICE); - jClass7.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + jClass7.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); microservice4.addJClass(jClass7); JClass jClass8 = new JClass("jClass8", "/jClass8","jClass8", ClassRole.CONTROLLER); jClass8.setMethods(Set.of(new Endpoint(new Method(), "/endpoint4", HttpMethod.GET))); diff --git a/src/test/java/unit/antipatterns/ServiceChainTest.java b/src/test/java/unit/antipatterns/ServiceChainTest.java index d3e236ca..609c31bd 100644 --- a/src/test/java/unit/antipatterns/ServiceChainTest.java +++ b/src/test/java/unit/antipatterns/ServiceChainTest.java @@ -85,7 +85,7 @@ public void serviceChainHasOne() { Microservice microservice2 = new Microservice("ms2", "/ms2"); JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); - jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); microservice2.addJClass(jClass2); JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); @@ -93,7 +93,7 @@ public void serviceChainHasOne() { Microservice microservice3 = new Microservice("ms3", "/ms3"); JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); - jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); microservice3.addJClass(jClass4); MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); @@ -113,12 +113,12 @@ public void serviceChainHasNoneCycle() { jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); microservice1.addJClass(jClass1); JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); - jClass5.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); microservice1.addJClass(jClass5); Microservice microservice2 = new Microservice("ms2", "/ms2"); JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); - jClass2.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); microservice2.addJClass(jClass2); JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); @@ -126,7 +126,7 @@ public void serviceChainHasNoneCycle() { Microservice microservice3 = new Microservice("ms3", "/ms3"); JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); - jClass4.setMethodCalls(Set.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); microservice3.addJClass(jClass4); JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); diff --git a/src/test/java/unit/extraction/ExtractionTest.java b/src/test/java/unit/extraction/ExtractionTest.java index cd07c57b..b30b5fb2 100644 --- a/src/test/java/unit/extraction/ExtractionTest.java +++ b/src/test/java/unit/extraction/ExtractionTest.java @@ -1,40 +1,55 @@ -//package unit.extraction; -// -//import edu.university.ecs.lab.common.config.ConfigUtil; -//import edu.university.ecs.lab.common.models.ir.JClass; -//import edu.university.ecs.lab.common.models.ir.RestCall; -//import edu.university.ecs.lab.common.services.GitService; -//import edu.university.ecs.lab.common.utils.SourceToObjectUtils; -//import org.junit.Before; -//import org.junit.Test; -// -//import java.io.File; -// -//import static org.junit.Assert.assertEquals; -//import static org.junit.Assert.assertTrue; -// -//public class ExtractionTest { -// private static final String TEST_FILE = "src/test/resources/TestFile.java"; -// private static final String TEST_CONFIG_FILE = "src/test/resources/test_config.json"; -// private static final int EXPECTED_CALLS = 6; -// private static final String PRE_URL = "/api/v1/seatservice/test"; -// -// @Before -// public void setUp() { -// } -// -// @Test -// public void restCallExtractionTest1() { -// JClass jClass = SourceToObjectUtils.parseClass(new File(TEST_FILE), ConfigUtil.readConfig(TEST_CONFIG_FILE), ""); -// -// assertEquals(EXPECTED_CALLS, jClass.getRestCalls().size()); +package unit.extraction; + +import edu.university.ecs.lab.common.config.ConfigUtil; +import edu.university.ecs.lab.common.models.ir.*; +import edu.university.ecs.lab.common.services.GitService; +import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; +import edu.university.ecs.lab.common.utils.SourceToObjectUtils; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ExtractionTest { + private static final String TEST_FILE1 = "src/test/resources/TestFile2.java"; + private static final String TEST_FILE2 = "src/test/resources/TestFile3.java"; + + + private static final String TEST_CONFIG_FILE = "src/test/resources/test_config.json"; + private static final int EXPECTED_CALLS = 6; + private static final String PRE_URL = "/api/v1/seatservice/test"; + + @Before + public void setUp() { + } + + @Test + public void restCallExtractionTest1() { + GitService gitService = new GitService(TEST_CONFIG_FILE); + MicroserviceSystem ms1 = JsonReadWriteUtils.readFromJSON("C:\\Users\\ninja\\IdeaProjects\\cimet2\\output\\java-microservice\\IR\\IR1_8948.json", MicroserviceSystem.class); + JClass jClass1 = SourceToObjectUtils.parseClass(new File(TEST_FILE1), ConfigUtil.readConfig(TEST_CONFIG_FILE), ""); + JClass jClass2 = SourceToObjectUtils.parseClass(new File(TEST_FILE2), ConfigUtil.readConfig(TEST_CONFIG_FILE), ""); + + for(Endpoint e : jClass1.getEndpoints()) { + for(RestCall rc : jClass2.getRestCalls()) { + if(RestCall.matchEndpoint(rc, e)) { + System.out.println("Passed " + rc.getUrl() + " " + e.getUrl()); + + } + } + + } + // // int i = 1; // for(RestCall restCall : jClass.getRestCalls()) { // assertTrue(restCall.getUrl().startsWith(PRE_URL + i++)); // } -// -// } -// -// -//} + + } + + +} diff --git a/src/test/resources/TestFile2.java b/src/test/resources/TestFile2.java new file mode 100644 index 00000000..d24a115f --- /dev/null +++ b/src/test/resources/TestFile2.java @@ -0,0 +1,108 @@ +package com.apssouza.controllers; + +import com.apssouza.services.TodoService; +import com.apssouza.entities.ToDo; +import com.apssouza.events.TodoCreatedEvent; +import com.apssouza.exceptions.DataNotFoundException; +import com.apssouza.infra.EventPublisher; +import com.fasterxml.jackson.databind.JsonNode; +import java.net.URI; +import java.util.List; +import java.util.Optional; +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +/** + * Controller responsible of accessing ToDo's functionalities + * + * @author apssouza + */ +@RequestMapping("/todos") +@RestController +public class TodoController { + + @Autowired + TodoService todoService; + + @Autowired + EventPublisher publisher; + + @GetMapping + public List all() { + return this.todoService.all(); + } + + @GetMapping("search") + public List getByUserEmail(@RequestParam("email") String email) { + return this.todoService.getByUserEmail(email); + } + + @PostMapping + public ResponseEntity save(@RequestBody @Valid ToDo todo, BindingResult result) { + if (result.hasErrors()) { + return ResponseEntity.badRequest().build(); + } + ToDo saved = this.todoService.save(todo); + Long id = saved.getId(); + if (id != null) { + URI location = ServletUriComponentsBuilder + .fromCurrentRequest().path("/{id}") + .buildAndExpand(id).toUri(); + return ResponseEntity.created(location).build(); + } + return ResponseEntity.noContent().build(); + } + + @PutMapping("{id}") + public ResponseEntity update( + @PathVariable long id, + @RequestBody @Valid ToDo toDo + ) { + return ResponseEntity.ok(todoService.update(id, toDo)); + } + + @GetMapping("{id}") + public ResponseEntity find(@PathVariable long id) { + Optional findById = todoService.findById(id); + return findById.map(todo -> { + return ResponseEntity.ok(todo); + }).orElseThrow(() -> new DataNotFoundException("Todo not found")); + } + + @DeleteMapping("{id}") + public ResponseEntity delete(@PathVariable long id) { + todoService.delete(id); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @PutMapping("{id}/status") + public ResponseEntity statusUpdate(@PathVariable long id, @RequestBody JsonNode statusUpdate) { + JsonNode status = statusUpdate.get("status"); + if (status == null) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST). + header("reason", "JSON should contains field done"). + build(); + } + ToDo todo = todoService.updateStatus( + id, + ToDo.TodoStatus.valueOf(status.asText()) + ); + return ResponseEntity.ok(todo); + } + +} \ No newline at end of file diff --git a/src/test/resources/TestFile3.java b/src/test/resources/TestFile3.java new file mode 100644 index 00000000..d9d3a7c2 --- /dev/null +++ b/src/test/resources/TestFile3.java @@ -0,0 +1,30 @@ +package com.apssouza.clients; + +import com.apssouza.pojos.Todo; +import com.apssouza.pojos.User; +import java.util.List; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Declarative To do REST client + * + * @author apssouza + */ +@FeignClient("todo") +public interface TodoClient { + + @RequestMapping(value = "/todos", method = RequestMethod.GET) + public List getAll(); + + @RequestMapping(value = "/todos/search", method = RequestMethod.GET) + public List getTodoByUserEmaill(@RequestParam("email") String email); + + @RequestMapping( + value = "/todos", + method = RequestMethod.POST + ) + Todo createTodo(Todo todo); +} \ No newline at end of file diff --git a/src/test/resources/test_config.json b/src/test/resources/test_config.json index 2d86cccc..9c3ce942 100644 --- a/src/test/resources/test_config.json +++ b/src/test/resources/test_config.json @@ -1,5 +1,5 @@ { - "systemName": "Train-ticket", + "systemName": "train-ticket", "repositoryURL": "https://github.com/FudanSELab/train-ticket.git", - "baseBranch": "main" + "baseBranch": "master" } \ No newline at end of file From 51555538d406f1bdca56835a73d14e147596b9fb Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Tue, 1 Oct 2024 15:23:41 -0500 Subject: [PATCH 08/11] Use Microservice getRestCall/getEndpoint methods --- .../ecs/lab/detection/architecture/models/AR3.java | 2 +- .../ecs/lab/detection/architecture/models/AR4.java | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java index 078090fa..3ae09342 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java @@ -128,7 +128,7 @@ private static boolean findMatch(RestCall restCall, MicroserviceSystem newSystem public static List scan2(MicroserviceSystem oldSystem, MicroserviceSystem newSystem) { List archRules = new ArrayList<>(); - List allRestCalls = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getServices().stream()).flatMap(jClass -> jClass.getRestCalls().stream()).collect(Collectors.toList()); + List allRestCalls = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getRestCalls().stream()).collect(Collectors.toList()); // For each restCall if we don't find a match in the new System diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java index 2948a596..778b677a 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java @@ -194,7 +194,7 @@ public static List scan2(MicroserviceSystem oldSystem, MicroserviceSystem n // If we are not removing or modifying a service // Get endpoints that do not have any calls - Set allEndpoints = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getControllers().stream()).flatMap(jClass -> jClass.getEndpoints().stream()).collect(Collectors.toSet()); + Set allEndpoints = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getEndpoints().stream()).collect(Collectors.toSet()); for(Endpoint endpoint: allEndpoints){ @@ -222,12 +222,11 @@ public static List scan2(MicroserviceSystem oldSystem, MicroserviceSystem n */ private static boolean findMatch(Endpoint endpoint, MicroserviceSystem newSystem) { for (Microservice microservice : newSystem.getMicroservices()) { - for (JClass service : microservice.getServices()) { - for (RestCall restcall : service.getRestCalls()) { - if (RestCall.matchEndpoint(restcall, endpoint)) { - return true; - } + for (RestCall restcall : microservice.getRestCalls()) { + if (RestCall.matchEndpoint(restcall, endpoint)) { + return true; } + } } return false; From f71e52e1a5276b2ec6bf7f2aa274bce87d71e087 Mon Sep 17 00:00:00 2001 From: maliaedmonds Date: Sun, 6 Oct 2024 16:53:22 -0700 Subject: [PATCH 09/11] updated documentation and pom.xml --- dependency-reduced-pom.xml | 161 ++++++++++++++++++ pom.xml | 42 ++++- .../ecs/lab/AllConfigsExcelRunner.java | 4 + .../ecs/lab/common/config/Config.java | 2 +- .../ecs/lab/common/error/Error.java | 3 +- .../common/models/enums/EndpointTemplate.java | 14 +- .../ecs/lab/common/models/enums/FileType.java | 3 + .../common/models/enums/RestCallTemplate.java | 12 +- .../ecs/lab/common/models/ir/Annotation.java | 11 ++ .../ecs/lab/common/models/ir/ConfigFile.java | 3 + .../ecs/lab/common/models/ir/Flow.java | 8 + .../ecs/lab/common/models/ir/Method.java | 6 + .../ecs/lab/common/models/ir/MethodCall.java | 7 + .../lab/common/models/ir/Microservice.java | 24 ++- .../common/models/ir/MicroserviceSystem.java | 12 ++ .../ecs/lab/common/models/ir/Parameter.java | 3 + .../ecs/lab/common/models/ir/RestCall.java | 15 ++ .../common/models/sdg/DependencyGraphI.java | 18 +- .../models/sdg/MethodDependencyGraph.java | 43 +++++ .../models/sdg/ServiceDependencyGraph.java | 23 +++ .../ecs/lab/common/services/GitService.java | 71 +++++++- .../lab/common/services/LoggerManager.java | 31 +++- .../ecs/lab/common/utils/FlowUtils.java | 51 ------ .../services/DeltaExtractionService.java | 5 + .../ecs/lab/detection/ExcelOutputRunner.java | 14 +- .../intermediate/utils/StringParserUtils.java | 33 ---- 26 files changed, 519 insertions(+), 100 deletions(-) create mode 100644 dependency-reduced-pom.xml diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 00000000..d774d3ed --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,161 @@ + + + 4.0.0 + edu.university.ecs.lab + cimet + 1.0-SNAPSHOT + + + + org.codehaus.mojo + exec-maven-plugin + 3.3.0 + + ${mainClass} + + + + maven-javadoc-plugin + 3.4.1 + + private + ./docs + ./docs + + + common + edu.university.ecs.lab.common.* + + + delta + edu.university.ecs.lab.delta.* + + + intermediate + edu.university.ecs.lab.intermediate.* + + + metrics + edu.university.ecs.lab.metrics.* + + + + + + maven-jar-plugin + 3.2.0 + + + + true + ${mainClass} + + + + + + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + ${mainClass} + + + + + + + + + + + run-ir + + exec:java + + + edu.university.ecs.lab.intermediate.create.IRExtractionRunner + + + + run-delta + + exec:java + + + edu.university.ecs.lab.delta.DeltaExtractionRunner + + + + run-merge + + exec:java + + + edu.university.ecs.lab.intermediate.merge.IRMergeRunner + + + + run-history + + exec:java + + + edu.university.ecs.lab.detection.ExcelOutputRunner + + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + junit + junit + 4.13.2 + test + + + hamcrest-core + org.hamcrest + + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + junit-jupiter-api + org.junit.jupiter + + + junit-jupiter-params + org.junit.jupiter + + + junit-jupiter-engine + org.junit.jupiter + + + + + + 16 + 16 + true + 3.8.1 + + diff --git a/pom.xml b/pom.xml index 5de3304b..11de296e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,11 +7,12 @@ cimet 1.0-SNAPSHOT - 11 - 11 + 16 + 16 true 3.8.1 + jar @@ -134,6 +135,43 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + ${mainClass} + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + jar-with-dependencies + + + + ${mainClass} + + + + + + assemble-all + package + + single + + + + diff --git a/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java b/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java index 21ba64f4..f9da9628 100644 --- a/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java +++ b/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java @@ -4,6 +4,10 @@ import java.io.File; import java.io.IOException; +/** + * Runs excel output runner for all config files in valid_configs directory + * NOTE: Must change the ExcelOutputRunner class to take config filepath as input args + */ public class AllConfigsExcelRunner { public static void main(String[] args) throws IOException { diff --git a/src/main/java/edu/university/ecs/lab/common/config/Config.java b/src/main/java/edu/university/ecs/lab/common/config/Config.java index 185aa5b0..96ba210c 100644 --- a/src/main/java/edu/university/ecs/lab/common/config/Config.java +++ b/src/main/java/edu/university/ecs/lab/common/config/Config.java @@ -44,7 +44,7 @@ public Config(String systemName, String repositoryURL, String branch) throws Exc } /** - * This method + * Check that config file is valid and has all required fields */ private void validateConfig(String systemName, String repositoryURL, String branch) { diff --git a/src/main/java/edu/university/ecs/lab/common/error/Error.java b/src/main/java/edu/university/ecs/lab/common/error/Error.java index 2e35e460..3daa7e9c 100644 --- a/src/main/java/edu/university/ecs/lab/common/error/Error.java +++ b/src/main/java/edu/university/ecs/lab/common/error/Error.java @@ -22,7 +22,8 @@ public enum Error { INVALID_JSON_READ(9, "Unable to read JSON from file!"), INVALID_JSON_WRITE(10, "Unable to write JSON to file!"), JPARSE_FAILED(10, "Failed to parse Java Code!"), - INVALID_CONFIG(10, "Invalid configuration file!"); + INVALID_CONFIG(10, "Invalid configuration file!"), + MISSING_CONFIG(10, "Missing configuration file!"); /** * The unique error code identifying the error type. diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java b/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java index aab5d24b..fa90fcf4 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java @@ -92,7 +92,12 @@ public EndpointTemplate(AnnotationExpr requestMapping, AnnotationExpr endpointMa } - + /** + * Method to get http method from mapping + * + * @param mapping mapping string for a given method + * @return HttpMethod object of same method type + */ private static HttpMethod httpFromMapping(String mapping) { switch (mapping) { case "GetMapping": @@ -116,6 +121,13 @@ private static HttpMethod httpFromMapping(String mapping) { } + /** + * Method to get endpoint path from annotations + * + * @param ae annotation expression from method + * @param url string formatted as a url + * @return endpoint path/url from annotation expression + */ public static String getPathFromAnnotation(AnnotationExpr ae, String url) { // Annotations of type @Mapping("/endpoint") if (ae.isSingleMemberAnnotationExpr()) { diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java b/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java index 217a2b45..5b3d69e2 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java @@ -1,5 +1,8 @@ package edu.university.ecs.lab.common.models.enums; +/** + * File types enum + */ public enum FileType { JCLASS, CONFIG, diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java b/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java index 90975621..ecab9dc1 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java @@ -150,6 +150,12 @@ private String backupParseURL(Expression exp) { } + /** + * Shorten URLs to only endpoint query + * + * @param str full url + * @return url query + */ private static String cleanURL(String str) { str = str.replace("http://", ""); str = str.replace("https://", ""); @@ -160,11 +166,7 @@ private static String cleanURL(String str) { str = str.substring(backslashNdx); } -// int questionNdx = str.indexOf("?"); -// if (questionNdx > 0) { -// str = str.substring(0, questionNdx); -// } - + // Remove any trailing quotes if (str.endsWith("\"")) { str = str.substring(0, str.length() - 1); } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java index 84ac1aaa..171b9f27 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java @@ -34,6 +34,11 @@ public Annotation(String name, String packageAndClassName, HashMap entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")); } @@ -54,6 +59,12 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Map attributes from annotation expression + * + * @param annotationExpr annotation expression object to parse + * @return map of annotation attributes and their values + */ private static HashMap parseAttributes(AnnotationExpr annotationExpr) { HashMap attributes = new HashMap<>(); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java b/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java index 67b074fe..971bb86a 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java @@ -5,6 +5,9 @@ import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Getter; +/** + * Represents a project configuration file + */ @Getter public class ConfigFile extends ProjectFile implements JsonSerializable { private final JsonObject data; diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java index 21e16e3a..afa51337 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java @@ -23,6 +23,9 @@ public class Flow implements JsonSerializable { private JClass repository; private Method repositoryMethod; + /** + * Create JSON object from flow object + */ @Override public JsonObject toJsonObject() { JsonObject jsonObject = new JsonObject(); @@ -41,6 +44,11 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Create JSON object from flow object with only names + * + * @return flow JSON object + */ public JsonObject toSmallJsonObject() { JsonObject jsonObject = new JsonObject(); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java index cd569a12..60852ea3 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java @@ -80,6 +80,12 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Get set of parameters from node list + * + * @param parameters Node list of javaparser parameter objects + * @return set of parameter objects + */ private Set parseParameters(NodeList parameters) { HashSet parameterSet = new HashSet<>(); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java b/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java index f1ba2703..2c18fb26 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java @@ -76,6 +76,13 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Checks if a method call matches a given method + * + * @param methodCall method call object to match + * @param method method object to match + * @return true if method call and method match, false otherwise + */ public static boolean matchMethod(MethodCall methodCall, Method method) { return methodCall.microserviceName.equals(method.microserviceName) && methodCall.objectType.equals(method.className) && methodCall.name.equals(method.name); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java index d8d98164..9b2aedf8 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java @@ -89,7 +89,6 @@ public JsonObject toJsonObject() { jsonObject.addProperty("path", path); jsonObject.add("controllers", JsonSerializable.toJsonArray(controllers)); jsonObject.add("entities", JsonSerializable.toJsonArray(entities)); -// jsonObject.add("embeddables", JsonSerializable.toJsonArray(embeddables)); jsonObject.add("feignClients", JsonSerializable.toJsonArray(feignClients)); jsonObject.add("services", JsonSerializable.toJsonArray(services)); jsonObject.add("repositories", JsonSerializable.toJsonArray(repositories)); @@ -99,6 +98,9 @@ public JsonObject toJsonObject() { } + /** + * see {@link JsonSerializable#toJsonArray(Iterable)} + */ private static JsonArray toJsonArray(Iterable list) { JsonArray jsonArray = new JsonArray(); for (JsonObject object : list) { @@ -273,20 +275,40 @@ public Set getAllFiles() { return set; } + /** + * This method returns all rest calls of a microservice + * + * @return the list of all rest calls + */ public List getRestCalls () { return getClasses().stream() .flatMap(jClass -> jClass.getRestCalls().stream()).collect(Collectors.toList()); } + /** + * This method returns all endpoints of a microservice + * + * @return the set of all endpoints + */ public Set getEndpoints () { return getControllers().stream().flatMap(controller -> controller.getEndpoints().stream()).collect(Collectors.toSet()); } + /** + * This method returns all method calls of a microservice + * + * @return the set of all method calls + */ public Set getMethodCalls () { return getClasses().stream().flatMap(jClass -> jClass.getMethodCalls().stream()).collect(Collectors.toSet()); } + /** + * This method returns all methods of a microservice + * + * @return the set of all methods + */ public Set getMethods () { return getClasses().stream().flatMap(jClass -> jClass.getMethods().stream()).collect(Collectors.toSet()); } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java b/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java index 1d43ef20..83a7c2d5 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java @@ -105,6 +105,12 @@ public void adopt(Microservice microservice) { } + /** + * Get the class of a given endpoint + * + * @param path endpoint + * @return class that endpoint is in + */ public JClass findClass(String path){ JClass returnClass = null; returnClass = getMicroservices().stream().flatMap(m -> m.getClasses().stream()).filter(c -> c.getPath().equals(path)).findFirst().orElse(null); @@ -115,6 +121,12 @@ public JClass findClass(String path){ return returnClass; } + /** + * Get the file of a given endpoint + * + * @param path endpoint + * @return file that endpoint is in + */ public ProjectFile findFile(String path){ ProjectFile returnFile = null; returnFile = getMicroservices().stream().flatMap(m -> m.getAllFiles().stream()).filter(c -> c.getPath().equals(path)).findFirst().orElse(null); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java index 7e2c2b1c..81a1b21b 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java @@ -7,6 +7,9 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * Represents a method call parameter + */ @Data public class Parameter extends Node implements JsonSerializable { diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java b/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java index 13e0433d..c8d28fdd 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java @@ -58,6 +58,13 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Checks if a rest call matches a given endpoint + * + * @param restcall rest call to match + * @param endpoint endpoint to match + * @return true if rest call and enpoint match, false otherwise + */ public static boolean matchEndpoint(RestCall restcall, Endpoint endpoint){ if(restcall.getMicroserviceName().equals(endpoint.getMicroserviceName())){ return false; @@ -68,6 +75,14 @@ public static boolean matchEndpoint(RestCall restcall, Endpoint endpoint){ return baseURL.equals(endpoint.getUrl()) && (restcall.getHttpMethod().equals(endpoint.getHttpMethod()) || endpoint.getHttpMethod().equals(HttpMethod.ALL)) && matchQueryParams(restcall, endpoint, queryParamIndex); } + /** + * Checks if rest call parameters match parameters for the target endpoint + * + * @param restCall rest call to match + * @param endpoint endpoint to match + * @param queryParamIndex string index at which query parameters start + * @return true if parameters match, false otherwise + */ private static boolean matchQueryParams(RestCall restCall, Endpoint endpoint, int queryParamIndex) { for(Parameter parameter : endpoint.getParameters()) { for(Annotation annotation : parameter.getAnnotations()) { diff --git a/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java b/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java index 02a2e998..c653b046 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java +++ b/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java @@ -11,6 +11,9 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * Represents an object dependency graph with dependent vertices connected by edges + */ public interface DependencyGraphI extends Graph, JsonSerializable { /** @@ -31,6 +34,11 @@ public interface DependencyGraphI extends Graph, JsonSerializable { */ boolean isMultigraph(); + /** + * Method to get adjacency list of the entire graph + * + * @return adjacency list of entire graph + */ default Map> getAdjacency() { return this.vertexSet().stream() .collect(Collectors.toMap( @@ -39,13 +47,21 @@ default Map> getAdjacency() { )); } + /** + * Method to get addjacency list of a given vertex + * + * @param vertex vertex to get adjacency list of + * @return adjacency list for given vertex + */ default Set getAdjacency(V vertex) { return this.outgoingEdgesOf(vertex).stream() .map(this::getEdgeTarget) .collect(Collectors.toSet()); } - + /** + * see {@link JsonSerializable#toJsonObject()} + */ default JsonObject toJsonObject() { JsonObject jsonObject = new JsonObject(); diff --git a/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java b/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java index d2bd7160..93be1597 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java +++ b/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java @@ -13,23 +13,48 @@ import java.util.Map; import java.util.Set; +/** + * Represents a dependency graph for service methods + */ @Getter public class MethodDependencyGraph extends DirectedWeightedPseudograph implements DependencyGraphI { + /** + * Represents the name of the graph + */ private final String label; + /** + * The timestamp of the current Network graph + * (i.e. the commit ID that the Network graph represents) + */ private final String timestamp; + /** + * Whether the edges are interpreted as directed + */ private final boolean directed = true; + /** + * Whether several edges between source and target are allowed + */ private final boolean multigraph = false; + /** + * Create a new method dependency graph from a given microservice system + * + * @param microserviceSystem microservice system from which to derive method dependency graph + */ public MethodDependencyGraph(MicroserviceSystem microserviceSystem) { super(DefaultWeightedEdge.class); this.label = microserviceSystem.getName(); this.timestamp = microserviceSystem.getCommitID(); + // Map of method details to method objects Map methods = new HashMap<>(); + + // Set of all method calls Set methodCalls = new HashSet<>(); + // Add method calls to set, map methods to microservice and class names microserviceSystem.getMicroservices() .forEach(ms -> { methodCalls.addAll(ms.getMethodCalls()); @@ -39,6 +64,7 @@ public MethodDependencyGraph(MicroserviceSystem microserviceSystem) { methods.put(method.getMicroserviceName() + method.getClassName() + method.getName(), method); });}); + // Add method/method call pairs to method dependency graph methods.values().forEach(targetMethod -> methodCalls.forEach(methodCall -> { Method sourceMethod = methods.get(methodCall.getMicroserviceName()+methodCall.getClassName()+methodCall.getCalledFrom()); @@ -59,6 +85,12 @@ public MethodDependencyGraph(MicroserviceSystem microserviceSystem) { } + /** + * Method to add and edge or update its weight if it already exists + * + * @param source method making the call + * @param target method being called + */ private void addUpdateEdge(Method source, Method target) { DefaultWeightedEdge edge = this.getEdge(source, target); if (edge == null) { @@ -93,6 +125,10 @@ public JsonObject toJsonObject() { return jsonObject; } + + /** + * Class to serialize a method as a json object + */ public static class MethodSerializer implements JsonSerializer { @Override public JsonElement serialize(Method method, Type type, JsonSerializationContext jsonSerializationContext) { @@ -101,6 +137,10 @@ public JsonElement serialize(Method method, Type type, JsonSerializationContext return jsonObject; } } + + /** + * Class to serialize an endpoint as a json object + */ public static class EndpointSerializer implements JsonSerializer { @Override public JsonElement serialize(Endpoint endpoint, Type type, JsonSerializationContext jsonSerializationContext) { @@ -110,6 +150,9 @@ public JsonElement serialize(Endpoint endpoint, Type type, JsonSerializationCont } } + /** + * Class to serialize an endpoint as a json object + */ public class MethodCallEdgeSerializer implements JsonSerializer { @Override public JsonElement serialize(DefaultWeightedEdge edge, Type typeOfSrc, JsonSerializationContext context) { diff --git a/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java b/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java index 218f95a3..75379571 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java +++ b/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java @@ -17,9 +17,23 @@ @Getter public class ServiceDependencyGraph extends DirectedWeightedMultigraph implements JsonSerializable, DependencyGraphI { + + /** + * Represents the name of the graph + */ private final String label; + /** + * The timestamp of the current Network graph + * (i.e. the commit ID that the Network graph represents) + */ private final String timestamp; + /** + * Whether the edges are interpreted as directed + */ private final boolean directed = true; + /** + * Whether several edges between source and target are allowed + */ private final boolean multigraph = true; /** @@ -61,6 +75,7 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { List restCalls = new ArrayList<>(); List endpoints = new ArrayList<>(); + // Add microservices, rest calls, and enpoints to respective lists, add microservice to graph as a vertex microserviceSystem.getMicroservices().forEach(microservice -> { ms.put(microservice.getName(), microservice); this.addVertex(microservice); @@ -70,6 +85,7 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { List> edgesList = new ArrayList<>(); + // Add services with matching rest call/endpoint pairs to edges list for (RestCall restCall : restCalls) { for (Endpoint endpoint : endpoints) { if (RestCall.matchEndpoint(restCall, endpoint)) { @@ -78,6 +94,7 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { } } + // Add edges to map edgesList.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).forEach((edgeData, value) -> { RestCallEdge edge = this.addEdge(ms.get(edgeData.get(0)), ms.get(edgeData.get(1))); @@ -86,6 +103,9 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { }); } + /** + * Class to serialize a microservice as a json object + */ public static class MicroserviceSerializer implements JsonSerializer { @Override public JsonElement serialize(Microservice microservice, Type type, JsonSerializationContext jsonSerializationContext) { @@ -95,6 +115,9 @@ public JsonElement serialize(Microservice microservice, Type type, JsonSerializa } } + /** + * Class to serialize a rest call as a json object + */ public class RestCallEdgeSerializer implements JsonSerializer { @Override public JsonElement serialize(RestCallEdge edge, Type typeOfSrc, JsonSerializationContext context) { diff --git a/src/main/java/edu/university/ecs/lab/common/services/GitService.java b/src/main/java/edu/university/ecs/lab/common/services/GitService.java index 5a712c93..9e72a04c 100644 --- a/src/main/java/edu/university/ecs/lab/common/services/GitService.java +++ b/src/main/java/edu/university/ecs/lab/common/services/GitService.java @@ -21,6 +21,9 @@ import java.util.Optional; import java.util.stream.Collectors; +/** + * Service to perform Git opperations + */ public class GitService { private static final int EXIT_SUCCESS = 0; private static final String HEAD_COMMIT = "HEAD"; @@ -28,6 +31,11 @@ public class GitService { private final Config config; private final Repository repository; + /** + * Create a Git service object from a project configuration file + * + * @param configPath path to project configuration file + */ public GitService(String configPath) { this.config = ConfigUtil.readConfig(configPath); FileUtils.makeDirs(); @@ -35,13 +43,18 @@ public GitService(String configPath) { this.repository = initRepository(); } + /** + * Method to clone a repository + */ public void cloneRemote() { String repositoryPath = FileUtils.getRepositoryPath(config.getRepoName()); + // Check if repository was already cloned if (new File(repositoryPath).exists()) { return; } + // Create and execute operating system process to clone repository try { ProcessBuilder processBuilder = new ProcessBuilder("git", "clone", config.getRepositoryURL(), repositoryPath); @@ -60,6 +73,11 @@ public void cloneRemote() { LoggerManager.info(() -> "Cloned repository " + config.getRepoName()); } + /** + * Method to reset repository to a given commit + * + * @param commitID commit id to reset to + */ public void resetLocal(String commitID) { validateLocalExists(); @@ -67,6 +85,7 @@ public void resetLocal(String commitID) { return; } + // Reset branch to old commit try (Git git = new Git(repository)) { git.reset().setMode(ResetCommand.ResetType.HARD).setRef(commitID).call(); } catch (Exception e) { @@ -76,6 +95,9 @@ public void resetLocal(String commitID) { LoggerManager.info(() -> "Set repository " + config.getRepoName() + " to " + commitID); } + /** + * Method to check that local directory exists + */ private void validateLocalExists() { File file = new File(FileUtils.getRepositoryPath(config.getRepoName())); if (!(file.exists() && file.isDirectory())) { @@ -83,6 +105,11 @@ private void validateLocalExists() { } } + /** + * Method to initialize repository from repository name + * + * @return file repository + */ public Repository initRepository() { validateLocalExists(); @@ -99,6 +126,13 @@ public Repository initRepository() { return repository; } + /** + * Method to get differences between old and new commits + * + * @param commitOld old commit id + * @param commitNew new commit id + * @return list of changes from old commit to new commit + */ public List getDifferences(String commitOld, String commitNew) { List returnList = null; RevCommit oldCommit = null, newCommit = null; @@ -144,6 +178,16 @@ public List getDifferences(String commitOld, String commitNew) { return returnList; } + /** + * Method to check if a commit difference was a change to the code + * + * @param diff DiffEntry object + * @param repository repository to check + * @param oldCommit old commit id + * @param newCommit new commit id + * + * @return true if difference was a change to the code, false otherwise + */ private boolean isCodeChange(DiffEntry diff, Repository repository, RevCommit oldCommit, RevCommit newCommit) { if((!diff.getOldPath().endsWith(".java") && !diff.getNewPath().endsWith(".java"))) { return true; @@ -161,6 +205,14 @@ private boolean isCodeChange(DiffEntry diff, Repository repository, RevCommit ol return !oldCode.equals(newCode); } + /** + * Get file data from a file tree + * + * @param repository repository to check + * @param treeId id of the tree to check + * @param filePath file to get data from + * @return data from the file, or an empty string if an error occurs or file is not found + */ private String getContentFromTree(Repository repository, ObjectId treeId, String filePath) { try (ObjectReader reader = repository.newObjectReader(); TreeWalk treeWalk = new TreeWalk(repository)) { @@ -191,11 +243,21 @@ private String getContentFromTree(Repository repository, ObjectId treeId, String return ""; } - + /** + * Remove comments and whitespace from file content + * + * @param content string of all file content + * @return string of file content with whitespace and comments removed + */ private String stripCommentsAndWhitespace(String content) { return content.replaceAll("(//.*|/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/|\\s+)", ""); } + /** + * Get Git log + * + * @return Git log as a list + */ public Iterable getLog() { Iterable returnList = null; @@ -208,6 +270,11 @@ public Iterable getLog() { return returnList; } + /** + * Get head commit for the repository + * + * @return commit id of head commit + */ public String getHeadCommit() { String commitID = ""; @@ -217,7 +284,7 @@ public String getHeadCommit() { ObjectId commitId = head.getObjectId(); RevCommit commit = walk.parseCommit(commitId); commitID = commit.getName(); - + walk.close(); } catch (Exception e) { Error.reportAndExit(Error.GIT_FAILED, Optional.of(e)); } diff --git a/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java b/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java index 62ef35c7..7bf5e225 100644 --- a/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java +++ b/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java @@ -8,27 +8,56 @@ import java.util.Optional; import java.util.function.Supplier; +/** + * Static functions to manage logger object + */ public class LoggerManager { private static final Logger logger = LogManager.getLogger(LoggerManager.class); + + /** + * Log an info message + * + * @param msgSupplier the message to log + */ public static void info(Supplier msgSupplier) { log(Level.INFO, msgSupplier); } + /** + * Log a warning message + * + * @param msgSupplier the message to log + */ public static void warn(Supplier msgSupplier) { log(Level.WARN, msgSupplier); } + /** + * Log a debug message + * + * @param msgSupplier the message to log + */ public static void debug(Supplier msgSupplier) { log(Level.DEBUG, msgSupplier); } + /** + * Log an error message + * + * @param msgSupplier the message to log + */ public static void error(Supplier msgSupplier, Optional exception) { log(Level.ERROR, msgSupplier); exception.ifPresent(e -> logger.error(e.getMessage(), e)); } - + /** + * Log message + * + * @param level the logging level + * @param msgSupplier the message to log + */ private static void log(Level level, Supplier msgSupplier) { logger.log(level, msgSupplier.get()); } diff --git a/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java index b0e3a7ed..578bb16b 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java @@ -85,57 +85,6 @@ public static List buildFlows(MicroserviceSystem microserviceSystem) { return allFlows; } -// public static List buildFlows(Map msModelMap) { -// // 1. get controller name & controller endpoint name -// List flows = generateNewFlows(getAllModelControllers(msModelMap)); -// -// for (Flow flow : flows) { -// // 2. get service method call in controller method -// Optional serviceMethodCall = Optional.ofNullable(findServiceMethodCall(flow)); -// if (serviceMethodCall.isPresent()) { -// flow.setServiceMethodCall(serviceMethodCall.get()); -// // 3. get service field variable in controller class by method call -// Optional serviceField = Optional.ofNullable(findServiceField(flow)); -// if (serviceField.isPresent()) { -// flow.setControllerServiceField(serviceField.get()); -// // 4. get service class -// Optional ServiceClass = Optional.ofNullable(findService(flow)); -// if (ServiceClass.isPresent()) { -// flow.setService(ServiceClass.get()); -// // 5. find service method name -// Optional ServiceMethod = Optional.ofNullable(findServiceMethod(flow)); -// if (ServiceMethod.isPresent()) { -// flow.setServiceMethod(ServiceMethod.get()); -// // 6. find method call in the service -// Optional repositoryMethodCall = -// Optional.ofNullable(findRepositoryMethodCall(flow)); -// if (repositoryMethodCall.isPresent()) { -// flow.setRepositoryMethodCall(repositoryMethodCall.get()); -// // 7. find repository variable -// Optional repositoryField = Optional.ofNullable(findRepositoryField(flow)); -// if (repositoryField.isPresent()) { -// flow.setServiceRepositoryField(repositoryField.get()); -// // 8. find repository class -// Optional repositoryClass = Optional.ofNullable(findRepository(flow)); -// if (repositoryClass.isPresent()) { -// flow.setRepository(repositoryClass.get()); -// // 9. find repository method -// Optional repositoryMethod = -// Optional.ofNullable(findRepositoryMethod(flow)); -// if (repositoryMethod.isPresent()) { -// flow.setRepositoryMethod(repositoryMethod.get()); -// } -// } -// } -// } -// } -// } -// } -// } -// } -// return flows; -// } - /** * This method returns a map of microservices to their controller classes * diff --git a/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java b/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java index 91c6de32..1fcb1548 100644 --- a/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java +++ b/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java @@ -97,6 +97,11 @@ public void generateDelta() { } + /** + * Process differences between commits + * + * @param diffEntries list of differences + */ public void processDelta(List diffEntries) { // Set up a new SystemChangeObject systemChange = new SystemChange(); diff --git a/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java b/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java index b7494818..66a22b7b 100644 --- a/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java +++ b/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java @@ -1,15 +1,27 @@ package edu.university.ecs.lab.detection; +import edu.university.ecs.lab.common.error.Error; + +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Optional; /** * Runner class to execute detection service */ public class ExcelOutputRunner { - public static void main(String[] args) throws IOException { String configPath = "./config.json"; + try { + File conifgFile = new File(configPath); + if (!conifgFile.exists()) { + throw new FileNotFoundException(); + } + } catch (Exception e) { + Error.reportAndExit(Error.MISSING_CONFIG, Optional.of(e)); + } DetectionService detectionService = new DetectionService(configPath); detectionService.runDetection(); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java b/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java index d7958bce..e3bbaa3f 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java @@ -28,39 +28,6 @@ public static String removeOuterQuotations(String s) { return s; } -// /** -// * Merge the given class and method paths into a single path. -// * -// *

ex: /abc/def and ghi/jkl --> abc/def/ghi/jkl -// * -// * @param classPath the class base (api) path -// * @param methodPath the method (api) path -// * @return the merged path -// */ -// public static String mergePaths(String classPath, String methodPath) { -// if (classPath.startsWith("/")) classPath = classPath.substring(1); -// if (methodPath.startsWith("/")) methodPath = methodPath.substring(1); -// -// String path = -// FilenameUtils.normalizeNoEndSeparator(FilenameUtils.concat(classPath, methodPath), true); -// if (!path.startsWith("/")) path = "/" + path; -// -// return path; -// } -// -// /** -// * Find the package name in the given compilation unit. -// * -// * @param cu the compilation unit -// * @return the package name else null if not found -// */ -// public static String findPackage(CompilationUnit cu) { -// for (PackageDeclaration pd : cu.findAll(PackageDeclaration.class)) { -// return pd.getNameAsString(); -// } -// return null; -// } - /** * Simplifies all path arguments to {?}. * From 463d2a0e1248a8395de85f317a585efca6c96792 Mon Sep 17 00:00:00 2001 From: Samantha Perry Date: Sun, 6 Oct 2024 17:59:31 -0700 Subject: [PATCH 10/11] added os setup and jar instructions to website --- docs/index.html | 44 ++++++++++++++++++++++++++++++----------- docs/setup-linux.html | 43 ++++++++++++++++++++++++++++++++++++++++ docs/setup-macos.html | 43 ++++++++++++++++++++++++++++++++++++++++ docs/setup-windows.html | 43 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 docs/setup-linux.html create mode 100644 docs/setup-macos.html create mode 100644 docs/setup-windows.html diff --git a/docs/index.html b/docs/index.html index 5ca3a4b7..46528bd4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -50,9 +50,9 @@

Project Moti

Setup

- - - + + +

Prerequisites

@@ -79,15 +79,35 @@

Installation

Running the Project

-

- After the installation is complete, you can run the project by executing the following command: -

    -
  • To Generate an IR: mvn clean compile -P run-ir
  • -
  • To Generate an Delta: mvn clean compile -P run-delta
  • -
  • To Generate an Merge: mvn clean compile -P run-merge
  • -
  • To Generate Historical Data: mvn clean compile -P run-history
  • -
-

+

+ After the installation is complete, you can run the project using either Maven or by executing the JAR file: +

    +
  • To Generate an IR: +
      +
    • Using Maven: mvn clean compile -P run-ir
    • +
    • Using JAR: java -jar cimet2-ir.jar
    • +
    +
  • +
  • To Generate a Delta: +
      +
    • Using Maven: mvn clean compile -P run-delta
    • +
    • Using JAR: java -jar cimet2-delta.jar
    • +
    +
  • +
  • To Generate a Merge: +
      +
    • Using Maven: mvn clean compile -P run-merge
    • +
    • Using JAR: java -jar cimet2-merge.jar
    • +
    +
  • +
  • To Generate Historical Data: +
      +
    • Using Maven: mvn clean compile -P run-history
    • +
    • Using JAR: java -jar cimet2-data.jar
    • +
    +
  • +
+

diff --git a/docs/setup-linux.html b/docs/setup-linux.html new file mode 100644 index 00000000..006626d9 --- /dev/null +++ b/docs/setup-linux.html @@ -0,0 +1,43 @@ + + + + + + CIMET2 - Linux Setup + + + + +
+

CIMET2 Linux Setup

+
+ +
+

Installing Git, Java, and Maven on Linux

+ +

Step 1: Install Git

+

Install Git using your package manager. For Ubuntu: sudo apt install git, or download from here.

+ +

Step 2: Install Java

+

Install OpenJDK: sudo apt install openjdk-17-jdk, or download from here.

+ +

Step 3: Install Maven

+

Install Maven: sudo apt install maven, or download from here.

+ +

Step 4: Running a JAR File

+

To run a JAR file, use the following command:

+

java -jar yourfile.jar

+
+ + + diff --git a/docs/setup-macos.html b/docs/setup-macos.html new file mode 100644 index 00000000..1d21fc23 --- /dev/null +++ b/docs/setup-macos.html @@ -0,0 +1,43 @@ + + + + + + CIMET2 - macOS Setup + + + + +
+

CIMET2 macOS Setup

+
+ +
+

Installing Git, Java, and Maven on macOS

+ +

Step 1: Install Git

+

Install Git using Homebrew by running: brew install git. You can also download from here.

+ +

Step 2: Install Java

+

Install Java using Homebrew: brew install java. Follow Oracle’s guide for manual installation.

+ +

Step 3: Install Maven

+

Install Maven using Homebrew: brew install maven, or download from here.

+ +

Step 4: Running a JAR File

+

To run a JAR file, use the following command:

+

java -jar yourfile.jar

+
+ + + diff --git a/docs/setup-windows.html b/docs/setup-windows.html new file mode 100644 index 00000000..d95f91f0 --- /dev/null +++ b/docs/setup-windows.html @@ -0,0 +1,43 @@ + + + + + + CIMET2 - Windows Setup + + + + +
+

CIMET2 Windows Setup

+
+ +
+

Installing Git, Java, and Maven on Windows

+ +

Step 1: Install Git

+

Download Git from here. Follow the installation instructions.

+ +

Step 2: Install Java

+

Download and install Java from here. Set the JAVA_HOME environment variable.

+ +

Step 3: Install Maven

+

Download Maven from here. Extract the files and add bin to your system's PATH.

+ +

Step 4: Running a JAR File

+

To run a JAR file, use the following command:

+

java -jar yourfile.jar

+
+ + + From ba17db594c754803cbd48fff5e62bd67d5085f07 Mon Sep 17 00:00:00 2001 From: Gabriel Goulis Date: Thu, 10 Oct 2024 18:42:51 -0500 Subject: [PATCH 11/11] Update website --- docs/deltaspec.json | 78 ------------------- docs/index.html | 71 +++++++---------- docs/ir-spec.json | 54 ------------- pom.xml | 14 ++++ .../ecs/lab/detection/DetectionService.java | 36 ++++----- .../java/integration/IRComparisonTest.java | 27 ++++--- .../antipatterns/CyclicDependencyTest.java | 3 + .../antipatterns/GreedyMicroserviceTest.java | 3 + .../antipatterns/HubLikeMicroserviceTest.java | 3 + .../unit/antipatterns/NoApiGatewayTest.java | 3 + .../unit/antipatterns/ServiceChainTest.java | 3 + .../java/unit/extraction/ExtractionTest.java | 3 + 12 files changed, 91 insertions(+), 207 deletions(-) delete mode 100644 docs/deltaspec.json delete mode 100644 docs/ir-spec.json diff --git a/docs/deltaspec.json b/docs/deltaspec.json deleted file mode 100644 index 8aefc021..00000000 --- a/docs/deltaspec.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "systemName": "train-ticket", - "version": "0.0.1", - "services": [ - { - "action": "MODIFY", - "id": "ms1111111", - "msName": "ts-service1", - "msPath": "train-ticket-microservices/ts-service1", - "startCommit": "abcdef123456789abcdef123456789", - "endCommit": "bbbbb44444444aaaaaaaa66666666", - "endpoints": [ - { - "action": "MODIFY", - "id": "endpoint1111111", - "api": "/api/v1/service1/routes", - "file": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/controller/RouteController.java", - "type": "@RequestMapping", - "httpMethod": "GET", - "methodName": "getRoutes", - "arguments": "[@RequestBody RouteInfo info, @RequestHeader HttpHeaders headers]", - "return": "java.util.List" - }, - { - "action": "ADD", - "id": "endpoint2222222", - "api": "/api/v1/service1/routes/{id}", - "file": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/controller/RouteController.java", - "type": "@RequestMapping", - "httpMethod": "GET", - "methodName": "getRouteById", - "arguments": "[@RequestBody RouteInfo info, @RequestHeader HttpHeaders headers]", - "return": "com.cloudhubs.trainticket.service1.entity.Route" - }, - { - "action": "REMOVE", - "id": "endpoint333333" - } - ], - "dependencies": { - "restDependencies": [ - { - "action": "ADD", - "id": "ms2222222", - "msName": "ts-service2", - "calls": [ - { - "action": "ADD", - "id": "endpoint4444444", - "sourceFile": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/service/impl/Service1Impl.java", - "sourceMethod": "methodNameWhereCallTookPlaceFrom" - } - ] - }, - { - "action": "REMOVE", - "id": "ms3333333" - } - ], - "dtoDependencies": [ - { - "action": "MODIFY", - "id": "ms4444444", - "msName": "ts-service4", - "sharedObjects": [ - { - "action": "REMOVE", - "id": "dto1234567" - } - ] - } - ], - "entityDependencies": [], - "constraintDependencies": [] - } - } - ] -} diff --git a/docs/index.html b/docs/index.html index 46528bd4..06133fa3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -50,21 +50,9 @@

Project Moti

Setup

- - - - -

Prerequisites

-

- Before you begin, ensure you have met the following requirements: -

-

+ + +

Installation

@@ -81,41 +69,36 @@

Installation

Running the Project

After the installation is complete, you can run the project using either Maven or by executing the JAR file: -

    -
  • To Generate an IR: -
      -
    • Using Maven: mvn clean compile -P run-ir
    • -
    • Using JAR: java -jar cimet2-ir.jar
    • -
    -
  • -
  • To Generate a Delta: -
      -
    • Using Maven: mvn clean compile -P run-delta
    • -
    • Using JAR: java -jar cimet2-delta.jar
    • -
    -
  • -
  • To Generate a Merge: -
      -
    • Using Maven: mvn clean compile -P run-merge
    • -
    • Using JAR: java -jar cimet2-merge.jar
    • -
    -
  • -
  • To Generate Historical Data: -
      -
    • Using Maven: mvn clean compile -P run-history
    • -
    • Using JAR: java -jar cimet2-data.jar
    • -
    -
  • -
+
    +
  • To Generate Historical Data: +
      +
    • Using Maven: mvn clean package -P run-history
    • +
    • Using JAR: java -jar cimet2-data.jar
    • +
    +
  • +
+ + Ensure you have a configuration file named "config.json" present in the same directory as the JAR. A sample configuration file is below... + +
+ + {
+ "systemName": "systemName",
+ "repositoryURL": "http://gitRepository.git",
+ "baseBranch": "master"
+ } +

-

Footnotes

-

-

+

Demo

+ +
+ +

For more information and documentation, click here.

diff --git a/docs/ir-spec.json b/docs/ir-spec.json deleted file mode 100644 index 07dda351..00000000 --- a/docs/ir-spec.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "systemName": "train-ticket", - "version": "0.0.1", - "services": [ - { - "id": "ts-service1", - "msName": "ts-service1", - "msPath": "train-ticket-microservices/ts-service1", - "commitId": "abcdef123456789abcdef123456789", - "endpoints": [ - { - "id": "GET:ts-service1.getRoutes#123", - "api": "/api/v1/service1/routes", - "file": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/controller/RouteController.java", - "type": "@RequestMapping", - "httpMethod": "GET", - "methodName": "getRoutes", - "arguments": "[@RequestBody RouteInfo info, @RequestHeader HttpHeaders headers]", - "return": "java.util.List" - }, - ... - ], - "dependencies": { - "restDependencies": [ - { - "id": "ms2222222", - "msName": "ts-service2", - "calls": [ - { - "id": "endpoint222222", - "sourceFile": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/service/impl/Service1Impl.java", - "sourceMethod": "methodNameWhereCallTookPlaceFrom" - }, - ... - ] - } - ... - ], - "dtoDependencies": [ - { - "id": "ms3333333", - "msName": "ts-service3", - "sharedObjects": [...] - }, - ... - ], - "entityDependencies": [...], - "constraintDepenencies": [...], - ... - } - }, - ... - ] -} diff --git a/pom.xml b/pom.xml index 11de296e..39395631 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,12 @@ gson 2.8.9 + + org.javatuples + javatuples + 1.2 + compile + org.eclipse.jgit org.eclipse.jgit @@ -135,6 +141,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + org.apache.maven.plugins maven-jar-plugin diff --git a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java index 576078f8..d17618a5 100644 --- a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java @@ -42,16 +42,16 @@ public class DetectionService { /** * Column labels for violation counts and metrics */ - private static final String[] columnLabels = new String[]{"Commit ID", "Greedy Microservices", "Hub-like Microservices", "Service Chains (MS level)", "Service Chains (Method level)", - "Wrong Cuts", "Cyclic Dependencies (MS level)", "Cyclic Dependencies (Method level)", "Wobbly Service Interactions", "No Healthchecks", + private static final String[] columnLabels = new String[]{"Commit ID", "Greedy Microservices", "Hub-like Microservices", "Service Chains", + "Wrong Cuts", "Cyclic Dependencies", "Wobbly Service Interactions", "No Healthchecks", "No API Gateway", "maxAIS", "avgAIS", "stdAIS", "maxADS", "ADCS", "stdADS", "maxACS", "avgACS", "stdACS", "SCF", "SIY", "maxSC", "avgSC", "stdSC", "SCCmodularity", "maxSIDC", "avgSIDC", "stdSIDC", "maxSSIC", "avgSSIC", "stdSSIC", - "maxLOMLC", "avgLOMLC", "stdLOMLC", "AR3 (System)","AR4 (System)", "AR6 (Change)", "AR7 (Change)"}; + "maxLOMLC", "avgLOMLC", "stdLOMLC"}; /** * Count of antipatterns, metrics, and architectural rules */ - private static final int ANTIPATTERNS = 10; + private static final int ANTIPATTERNS = 8; private static final int METRICS = 24; private static final int ARCHRULES = 4; @@ -169,18 +169,16 @@ public void runDetection() { updateAntiPatterns(currIndex, antipatterns); updateMetrics(currIndex, metrics); - - // For simplicity we will skip rules on the last iteration since there is no newSystem - if(i < commits.size() - 1) { - arDetectionService = new ARDetectionService(systemChange, oldSystem, newSystem); - rules = arDetectionService.scanUseCases(); - - updateRules(nextIndex, rules); - } else { - continue; - } } + // For simplicity we will skip rules on the last iteration since there is no newSystem +// if(i < commits.size() - 1) { +// arDetectionService = new ARDetectionService(systemChange, oldSystem, newSystem); +// rules = arDetectionService.scanUseCases(); +// +// updateRules(nextIndex, rules); +// } + // After completing this iteration, we can replace oldIR with newIR // try { // Files.move(Paths.get(NEW_IR_PATH), Paths.get(OLD_IR_PATH), StandardCopyOption.REPLACE_EXISTING); @@ -193,7 +191,7 @@ public void runDetection() { // At the end we write the workbook to file try (FileOutputStream fileOut = new FileOutputStream(String.format("./output/%s/output-%s.xlsx",config.getRepoName(), config.getSystemName()))) { workbook.write(fileOut); - System.out.printf("Excel file created: AntiPatterns_%s.xlsx%n", config.getSystemName()); +// System.out.printf("Excel file created: AntiPatterns_%s.xlsx%n", config.getSystemName()); workbook.close(); } catch (Exception e) { e.printStackTrace(); @@ -257,11 +255,11 @@ private void detectAntipatterns(MicroserviceSystem microserviceSystem, Map