From f6b45e0fc2203f883d818bc1dd82d511205b64fa Mon Sep 17 00:00:00 2001
From: Jan Thurner <107639007+Jan-Thurner@users.noreply.github.com>
Date: Mon, 19 Aug 2024 13:54:18 +0200
Subject: [PATCH] Development: Analyze REST calls and endpoints (#8771)

---
 .../analysis-of-endpoint-connections.yml      |  79 ++++---
 settings.gradle                               |   1 +
 .../build.gradle                              |  24 +-
 ...{eslint.conjig.json => eslint.config.json} |   7 +-
 .../AnalysisOfEndpointConnections.java        |  83 -------
 .../endpointanalysis/EndpointAnalysis.java    |   6 +
 .../endpointanalysis/EndpointAnalyzer.java    | 144 ++++++++++++
 .../EndpointClassInformation.java             |   6 +
 .../endpointanalysis/EndpointInformation.java |  36 +++
 .../cit/endpointanalysis/EndpointParser.java  | 217 ++++++++++++++++++
 .../endpointanalysis/RestCallAnalysis.java    |   6 +
 .../endpointanalysis/RestCallAnalyzer.java    | 144 ++++++++++++
 .../RestCallFileInformation.java              |   4 +
 .../endpointanalysis/RestCallInformation.java |  18 ++
 .../RestCallWithMatchingEndpoint.java         |   4 +
 .../cit/endpointanalysis/UsedEndpoints.java   |   6 +
 .../AnalysisOfEndpointConnectionsClient.ts    |  53 ++++-
 .../src/main/typeScript/Postprocessor.ts      |  22 +-
 .../src/main/typeScript/Preprocessor.ts       |  26 +--
 19 files changed, 729 insertions(+), 157 deletions(-)
 rename supporting_scripts/analysis-of-endpoint-connections/{eslint.conjig.json => eslint.config.json} (72%)
 delete mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/AnalysisOfEndpointConnections.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalysis.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalyzer.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointClassInformation.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointInformation.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointParser.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalysis.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalyzer.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallFileInformation.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallInformation.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallWithMatchingEndpoint.java
 create mode 100644 supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/UsedEndpoints.java

diff --git a/.github/workflows/analysis-of-endpoint-connections.yml b/.github/workflows/analysis-of-endpoint-connections.yml
index 25849bddd012..2116605bea0a 100644
--- a/.github/workflows/analysis-of-endpoint-connections.yml
+++ b/.github/workflows/analysis-of-endpoint-connections.yml
@@ -1,12 +1,8 @@
+name: Analysis of Endpoint Connections
+
 on:
   workflow_dispatch:
-  pull_request:
-    types:
-      - opened
-      - synchronize
-    paths:
-      - 'src/main/java/**'
-      - 'src/main/webapp/**'
+  push:
 
 # Keep in sync with build.yml and test.yml and codeql-analysis.yml
 env:
@@ -15,7 +11,7 @@ env:
   java: 21
 
 jobs:
-  analysis-of-endpoint-connections:
+  Parse-rest-calls-and-endpoints:
     timeout-minutes: 10
     runs-on: ubuntu-latest
     steps:
@@ -24,39 +20,70 @@ jobs:
         with:
           fetch-depth: 0
 
-      - name: Get list of modified files
-        run: |
-          git diff --name-only origin/${{ github.event.pull_request.base.ref }} HEAD > modified_files.txt
+      - name: Set up JDK 21
+        uses: actions/setup-java@v4
+        with:
+          java-version: '${{ env.java }}'
+          distribution: 'temurin'
+          cache: 'gradle'
 
-      # Analyze the client sided REST-API calls
-      - name: Set up Node.js
+      - name: Set up node.js
         uses: actions/setup-node@v4
         with:
           node-version: '${{ env.node }}'
 
-      - name: Install and compile TypeScript
+      - name: Parse client sided REST-API calls
         run: |
-          cd supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/
           npm install
-          tsc -p tsconfig.analysisOfEndpointConnections.json
-
-      - name: Run analysis-of-endpoint-connections-client
-        run: |
+          tsc -p supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/tsconfig.analysisOfEndpointConnections.json
           node supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/AnalysisOfEndpointConnectionsClient.js
 
-      - name: Upload JSON file
+      - name: Parse server sided Endpoints
+        run: ./gradlew :supporting_scripts:analysis-of-endpoint-connections:runEndpointParser
+
+      - name: Upload parsing results
         uses: actions/upload-artifact@v4
         with:
-          name: rest-calls-json
-          path: supporting_scripts/analysis-of-endpoint-connections/restCalls.json
+          name: REST API Parsing Results
+          path: |
+            supporting_scripts/analysis-of-endpoint-connections/endpoints.json
+            supporting_scripts/analysis-of-endpoint-connections/restCalls.json
+
+  Analysis-of-endpoint-connections:
+    needs: Parse-rest-calls-and-endpoints
+    timeout-minutes: 10
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
 
-      # Analyze the server sided endpoints
       - name: Set up JDK 21
         uses: actions/setup-java@v4
         with:
           distribution: 'temurin'
           java-version: '${{ env.java }}'
+          cache: 'gradle'
 
-      - name: Run analysis-of-endpoint-connections
-        run: |
-          ./gradlew :supporting_scripts:analysis-of-endpoint-connections:run --args="$(cat modified_files.txt)"
+      - name: Download JSON files
+        uses: actions/download-artifact@v4
+        with:
+          name: REST API Parsing Results
+          path: supporting_scripts/analysis-of-endpoint-connections/
+
+      - name: Analyze endpoints
+        run:
+          ./gradlew :supporting_scripts:analysis-of-endpoint-connections:runEndpointAnalysis
+
+      - name: Analyze rest calls
+        run:
+          ./gradlew :supporting_scripts:analysis-of-endpoint-connections:runRestCallAnalysis
+
+      - name: Upload analysis results
+        uses: actions/upload-artifact@v4
+        with:
+          name: Endpoint and REST Call Analysis Results
+          path: |
+            supporting_scripts/analysis-of-endpoint-connections/endpointAnalysisResult.json
+            supporting_scripts/analysis-of-endpoint-connections/restCallAnalysisResult.json
diff --git a/settings.gradle b/settings.gradle
index b9f9d365979c..c99752089fcd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -14,6 +14,7 @@ pluginManagement {
 
 rootProject.name = 'Artemis'
 
+// needed for rest call and endpoint analysis
 include 'supporting_scripts:analysis-of-endpoint-connections'
 
 // needed for programming exercise templates
diff --git a/supporting_scripts/analysis-of-endpoint-connections/build.gradle b/supporting_scripts/analysis-of-endpoint-connections/build.gradle
index ef2fa37c2f5a..0e41c785d65c 100644
--- a/supporting_scripts/analysis-of-endpoint-connections/build.gradle
+++ b/supporting_scripts/analysis-of-endpoint-connections/build.gradle
@@ -13,20 +13,26 @@ repositories {
 evaluationDependsOn(':')
 
 dependencies {
-    implementation rootProject.ext.qDoxVersionReusable
+    implementation 'com.github.javaparser:javaparser-symbol-solver-core:3.26.0'
+    implementation 'com.github.javaparser:javaparser-core:3.26.0'
+    implementation 'com.github.javaparser:javaparser-core-serialization:3.26.0'
+    implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
     implementation rootProject.ext.springBootStarterWeb
+    implementation 'org.slf4j:slf4j-api:1.7.32'
+    implementation 'ch.qos.logback:logback-classic:1.2.6'
 }
 
-test {
-    useJUnitPlatform()
+task runEndpointParser(type: JavaExec) {
+    classpath = sourceSets.main.runtimeClasspath
+    mainClass = 'de.tum.cit.endpointanalysis.EndpointParser'
 }
 
-application {
-    mainClassName = 'de.tum.cit.endpointanalysis.AnalysisOfEndpointConnections'
+task runEndpointAnalysis(type: JavaExec) {
+    classpath = sourceSets.main.runtimeClasspath
+    mainClass = 'de.tum.cit.endpointanalysis.EndpointAnalyzer'
 }
 
-run {
-    if (project.hasProperty('appArgs')) {
-        args = project.appArgs.split(' ')
-    }
+task runRestCallAnalysis(type: JavaExec) {
+    classpath = sourceSets.main.runtimeClasspath
+    mainClass = 'de.tum.cit.endpointanalysis.RestCallAnalyzer'
 }
diff --git a/supporting_scripts/analysis-of-endpoint-connections/eslint.conjig.json b/supporting_scripts/analysis-of-endpoint-connections/eslint.config.json
similarity index 72%
rename from supporting_scripts/analysis-of-endpoint-connections/eslint.conjig.json
rename to supporting_scripts/analysis-of-endpoint-connections/eslint.config.json
index 0b47d7836035..393e1c0972cf 100644
--- a/supporting_scripts/analysis-of-endpoint-connections/eslint.conjig.json
+++ b/supporting_scripts/analysis-of-endpoint-connections/eslint.config.json
@@ -10,10 +10,5 @@
             "jsx": true
         }
     },
-    "rules": {},
-    "settings": {
-        "react": {
-            "version": "detect"
-        }
-    }
+    "rules": {}
 }
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/AnalysisOfEndpointConnections.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/AnalysisOfEndpointConnections.java
deleted file mode 100644
index b47b91d680d8..000000000000
--- a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/AnalysisOfEndpointConnections.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package de.tum.cit.endpointanalysis;
-
-import java.io.File;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PatchMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-import com.thoughtworks.qdox.JavaProjectBuilder;
-import com.thoughtworks.qdox.model.JavaAnnotation;
-import com.thoughtworks.qdox.model.JavaClass;
-import com.thoughtworks.qdox.model.JavaMethod;
-
-public class AnalysisOfEndpointConnections {
-
-    /**
-     * This is the entry point of the analysis of server sided endpoints.
-     *
-     * @param args List of files that should be analyzed regarding endpoints.
-     */
-    public static void main(String[] args) {
-        if (args.length == 0) {
-            System.out.println("No files to analyze.");
-            return;
-        }
-        String[] filePaths = args[0].split("\n");
-        String[] serverFiles = Arrays.stream(filePaths).map(filePath -> Paths.get("..", "..", filePath).toString())
-                .filter(filePath -> Files.exists(Paths.get(filePath)) && filePath.endsWith(".java")).toArray(String[]::new);
-        analyzeServerEndpoints(serverFiles);
-    }
-
-    private static void analyzeServerEndpoints(String[] filePaths) {
-        final Set<String> httpMethodClasses = Set.of(GetMapping.class.getName(), PostMapping.class.getName(), PutMapping.class.getName(), DeleteMapping.class.getName(),
-                PatchMapping.class.getName(), RequestMapping.class.getName());
-
-        JavaProjectBuilder builder = new JavaProjectBuilder();
-        for (String filePath : filePaths) {
-            builder.addSourceTree(new File(filePath));
-        }
-
-        Collection<JavaClass> classes = builder.getClasses();
-        for (JavaClass javaClass : classes) {
-            Optional<JavaAnnotation> requestMappingOptional = javaClass.getAnnotations().stream()
-                    .filter(annotation -> annotation.getType().getFullyQualifiedName().equals(RequestMapping.class.getName())).findFirst();
-
-            boolean hasEndpoint = javaClass.getMethods().stream().flatMap(method -> method.getAnnotations().stream())
-                    .anyMatch(annotation -> httpMethodClasses.contains(annotation.getType().getFullyQualifiedName()));
-
-            if (hasEndpoint) {
-                System.out.println("==================================================");
-                System.out.println("Class: " + javaClass.getFullyQualifiedName());
-                requestMappingOptional.ifPresent(annotation -> System.out.println("Class Request Mapping: " + annotation.getProperty("value")));
-                System.out.println("==================================================");
-            }
-
-            for (JavaMethod method : javaClass.getMethods()) {
-                for (JavaAnnotation annotation : method.getAnnotations()) {
-                    if (httpMethodClasses.contains(annotation.getType().getFullyQualifiedName())) {
-                        System.out.println("Endpoint: " + method.getName());
-                        System.out.println(
-                                annotation.getType().getFullyQualifiedName().equals(RequestMapping.class.getName()) ? "RequestMapping·method: " + annotation.getProperty("method")
-                                        : "HTTP method annotation: " + annotation.getType().getName());
-                        System.out.println("Path: " + annotation.getProperty("value"));
-                        System.out.println("Line: " + method.getLineNumber());
-                        List<String> annotations = method.getAnnotations().stream().filter(a -> !a.equals(annotation)).map(a -> a.getType().getName()).toList();
-                        System.out.println("Other annotations: " + annotations);
-                        System.out.println("---------------------------------------------------");
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalysis.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalysis.java
new file mode 100644
index 000000000000..4c38f7e826e8
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalysis.java
@@ -0,0 +1,6 @@
+package de.tum.cit.endpointanalysis;
+
+import java.util.List;
+
+public record EndpointAnalysis(List<UsedEndpoints> usedEndpoints, List<EndpointInformation> unusedEndpoints) {
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalyzer.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalyzer.java
new file mode 100644
index 000000000000..250966bfd432
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointAnalyzer.java
@@ -0,0 +1,144 @@
+package de.tum.cit.endpointanalysis;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class EndpointAnalyzer {
+
+    private static String EndpointAnalysisResultPath = "endpointAnalysisResult.json";
+
+    private static final Logger logger = LoggerFactory.getLogger(EndpointAnalyzer.class);
+
+    public static void main(String[] args) {
+        analyzeEndpoints();
+        printEndpointAnalysisResult();
+    }
+
+    /**
+     * Analyzes server side endpoints and matches them with client side REST calls.
+     *
+     * This method reads endpoint and REST call information from JSON files,
+     * compares them to find matching REST calls for each endpoint, and writes
+     * the analysis result to a JSON file. Endpoints without matching REST calls
+     * are also recorded.
+     */
+    private static void analyzeEndpoints() {
+        ObjectMapper mapper = new ObjectMapper();
+
+        try {
+            List<EndpointClassInformation> endpointClasses = mapper.readValue(new File(EndpointParser.ENDPOINT_PARSING_RESULT_PATH),
+                    new TypeReference<List<EndpointClassInformation>>() {
+                    });
+            List<RestCallFileInformation> restCallFiles = mapper.readValue(new File(EndpointParser.REST_CALL_PARSING_RESULT_PATH),
+                    new TypeReference<List<RestCallFileInformation>>() {
+                    });
+
+            List<UsedEndpoints> endpointsAndMatchingRestCalls = new ArrayList<>();
+            List<EndpointInformation> unusedEndpoints = new ArrayList<>();
+
+            Map<String, List<RestCallInformation>> restCallMap = new HashMap<>();
+
+            // Populate the map with rest calls
+            for (RestCallFileInformation restCallFile : restCallFiles) {
+                for (RestCallInformation restCall : restCallFile.restCalls()) {
+                    String restCallURI = restCall.buildComparableRestCallUri();
+                    restCallMap.computeIfAbsent(restCallURI, uri -> new ArrayList<>()).add(restCall);
+                }
+            }
+
+            for (EndpointClassInformation endpointClass : endpointClasses) {
+                for (EndpointInformation endpoint : endpointClass.endpoints()) {
+
+                    String endpointURI = endpoint.buildComparableEndpointUri();
+                    List<RestCallInformation> matchingRestCalls = restCallMap.getOrDefault(endpointURI, new ArrayList<>());
+
+                    // Check for wildcard endpoints if no exact match is found
+                    checkForWildcardEndpoints(endpoint, matchingRestCalls, endpointURI, restCallMap);
+
+                    if (matchingRestCalls.isEmpty()) {
+                        unusedEndpoints.add(endpoint);
+                    }
+                    else {
+                        endpointsAndMatchingRestCalls.add(new UsedEndpoints(endpoint, matchingRestCalls, endpointClass.filePath()));
+                    }
+                }
+            }
+
+            EndpointAnalysis endpointAnalysis = new EndpointAnalysis(endpointsAndMatchingRestCalls, unusedEndpoints);
+            mapper.writeValue(new File(EndpointAnalysisResultPath), endpointAnalysis);
+        }
+        catch (IOException e) {
+            logger.error("Failed to analyze endpoints", e);
+        }
+    }
+
+    /**
+     * Checks for wildcard endpoints and adds matching REST calls to the list.
+     *
+     * This method is used to find matching REST calls for endpoints that use wildcard URIs.
+     * If no exact match is found for an endpoint, it checks if the endpoint URI ends with a wildcard ('*').
+     * It then iterates through the rest call map to find URIs that start with the same prefix as the endpoint URI
+     * (excluding the wildcard) and have the same HTTP method. If such URIs are found, they are added to the list of matching REST calls.
+     *
+     * @param endpoint          The endpoint information to check for wildcard matches.
+     * @param matchingRestCalls The list of matching REST calls to be populated.
+     * @param endpointURI       The URI of the endpoint being checked.
+     * @param restCallMap       The map of rest call URIs to their corresponding information.
+     */
+    private static void checkForWildcardEndpoints(EndpointInformation endpoint, List<RestCallInformation> matchingRestCalls, String endpointURI,
+            Map<String, List<RestCallInformation>> restCallMap) {
+        if (matchingRestCalls.isEmpty() && endpointURI.endsWith("*")) {
+            for (String uri : restCallMap.keySet()) {
+                if (uri.startsWith(endpoint.buildComparableEndpointUri().substring(0, endpoint.buildComparableEndpointUri().length() - 1))
+                        && endpoint.getHttpMethod().toLowerCase().equals(restCallMap.get(uri).get(0).method().toLowerCase())) {
+                    matchingRestCalls.addAll(restCallMap.get(uri));
+                }
+            }
+        }
+    }
+
+    /**
+     * Prints the endpoint analysis result.
+     *
+     * This method reads the endpoint analysis result from a JSON file and prints
+     * the details of unused endpoints to the console. The details include the
+     * endpoint URI, HTTP method, file path, and line number. If no matching REST
+     * call is found for an endpoint, it prints a message indicating this.
+     */
+    private static void printEndpointAnalysisResult() {
+        ObjectMapper mapper = new ObjectMapper();
+        EndpointAnalysis endpointsAndMatchingRestCalls = null;
+        try {
+            endpointsAndMatchingRestCalls = mapper.readValue(new File(EndpointAnalysisResultPath), new TypeReference<EndpointAnalysis>() {
+            });
+        }
+        catch (IOException e) {
+            logger.error("Failed to deserialize endpoint analysis result", e);
+            return;
+        }
+
+        endpointsAndMatchingRestCalls.unusedEndpoints().stream().forEach(endpoint -> {
+            logger.info("=============================================");
+            logger.info("Endpoint URI: {}", endpoint.buildCompleteEndpointURI());
+            logger.info("HTTP method: {}", endpoint.httpMethodAnnotation());
+            logger.info("File path: {}", endpoint.className());
+            logger.info("Line: {}", endpoint.line());
+            logger.info("=============================================");
+            logger.info("No matching REST call found for endpoint: {}", endpoint.buildCompleteEndpointURI());
+            logger.info("---------------------------------------------");
+            logger.info("");
+        });
+
+        logger.info("Number of endpoints without matching REST calls: {}", endpointsAndMatchingRestCalls.unusedEndpoints().size());
+    }
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointClassInformation.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointClassInformation.java
new file mode 100644
index 000000000000..062a3671b1bf
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointClassInformation.java
@@ -0,0 +1,6 @@
+package de.tum.cit.endpointanalysis;
+
+import java.util.List;
+
+public record EndpointClassInformation(String filePath, String classRequestMapping, List<EndpointInformation> endpoints) {
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointInformation.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointInformation.java
new file mode 100644
index 000000000000..19b32dc8881b
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointInformation.java
@@ -0,0 +1,36 @@
+package de.tum.cit.endpointanalysis;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+public record EndpointInformation(String requestMapping, String endpoint, String httpMethodAnnotation, String URI, String className, int line, List<String> otherAnnotations) {
+
+    public String buildCompleteEndpointURI() {
+        StringBuilder result = new StringBuilder();
+        if (this.requestMapping != null && !this.requestMapping.isEmpty()) {
+            // Remove quotes from the requestMapping as they are used to define the String in the source code but are not part of the URI
+            result.append(this.requestMapping.replace("\"", ""));
+        }
+        // Remove quotes from the URI as they are used to define the String in the source code but are not part of the URI
+        result.append(this.URI.replace("\"", ""));
+        return result.toString();
+    }
+
+    String buildComparableEndpointUri() {
+        // Replace arguments with placeholder
+        return this.buildCompleteEndpointURI().replaceAll("\\{.*?\\}", ":param:");
+    }
+
+    @JsonIgnore
+    public String getHttpMethod() {
+        return switch (this.httpMethodAnnotation) {
+            case "GetMapping" -> "get";
+            case "PostMapping" -> "post";
+            case "PutMapping" -> "put";
+            case "DeleteMapping" -> "delete";
+            case "PatchMapping" -> "patch";
+            default -> "No HTTP method annotation found";
+        };
+    }
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointParser.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointParser.java
new file mode 100644
index 000000000000..b0ab2cdb1f0b
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/EndpointParser.java
@@ -0,0 +1,217 @@
+package de.tum.cit.endpointanalysis;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.javaparser.ParserConfiguration;
+import com.github.javaparser.StaticJavaParser;
+import com.github.javaparser.ast.CompilationUnit;
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
+import com.github.javaparser.ast.expr.AnnotationExpr;
+import com.github.javaparser.ast.expr.ArrayInitializerExpr;
+import com.github.javaparser.ast.expr.Expression;
+import com.github.javaparser.ast.expr.NormalAnnotationExpr;
+import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
+
+public class EndpointParser {
+
+    static final String ENDPOINT_PARSING_RESULT_PATH = "endpoints.json";
+
+    static final String REST_CALL_PARSING_RESULT_PATH = "restCalls.json";
+
+    private static final Logger logger = LoggerFactory.getLogger(EndpointParser.class);
+
+    public static void main(String[] args) {
+        final Path absoluteDirectoryPath = Path.of("../../src/main/java").toAbsolutePath().normalize();
+
+        StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21);
+
+        String[] filesToParse = {};
+        try (Stream<Path> paths = Files.walk(absoluteDirectoryPath)) {
+            filesToParse = paths.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".java")).map(Path::toString).toArray(String[]::new);
+        }
+        catch (IOException e) {
+            logger.error("Error reading files from directory: {}", absoluteDirectoryPath, e);
+        }
+
+        parseServerEndpoints(filesToParse);
+    }
+
+    /**
+     * Parses server endpoints from the given file paths.
+     *
+     * This method reads Java files from the specified file paths, extracts endpoint
+     * information annotated with HTTP method annotations, and writes the parsed
+     * endpoint information to a JSON file. It also logs any files that failed to parse.
+     *
+     * @param filePaths an array of file paths to parse for endpoint information
+     */
+    private static void parseServerEndpoints(String[] filePaths) {
+        List<EndpointClassInformation> endpointClasses = new ArrayList<>();
+        final Set<String> httpMethodClasses = Set.of(GetMapping.class.getSimpleName(), PostMapping.class.getSimpleName(), PutMapping.class.getSimpleName(),
+                DeleteMapping.class.getSimpleName(), PatchMapping.class.getSimpleName(), RequestMapping.class.getSimpleName());
+        List<String> filesFailedToParse = new ArrayList<>();
+
+        for (String filePath : filePaths) {
+            CompilationUnit compilationUnit;
+            try {
+                compilationUnit = StaticJavaParser.parse(new File(filePath));
+            }
+            catch (Exception e) {
+                filesFailedToParse.add(filePath);
+                continue;
+            }
+
+            List<ClassOrInterfaceDeclaration> classes = compilationUnit.findAll(ClassOrInterfaceDeclaration.class);
+            for (ClassOrInterfaceDeclaration javaClass : classes) {
+                List<EndpointInformation> endpoints = new ArrayList<>();
+                final String classRequestMappingString = extractClassRequestMapping(javaClass, httpMethodClasses);
+
+                endpoints.addAll(extractAnnotationPathValues(javaClass, httpMethodClasses, classRequestMappingString));
+
+                if (!endpoints.isEmpty()) {
+                    endpointClasses.add(new EndpointClassInformation(javaClass.getNameAsString(), classRequestMappingString, endpoints));
+                }
+            }
+        }
+
+        printFilesFailedToParse(filesFailedToParse);
+
+        writeEndpointsToFile(endpointClasses);
+    }
+
+    /**
+     * Extracts endpoint information from the methods of a given class declaration.
+     *
+     * This method iterates over the methods of the provided class and their annotations.
+     * If an annotation matches one of the specified HTTP method annotations, it extracts
+     * the path values from the annotation and creates EndpointInformation objects for each path.
+     *
+     * @param javaClass                 the class declaration to extract endpoint information from
+     * @param httpMethodClasses         a set of HTTP method annotation class names
+     * @param classRequestMappingString the class-level request mapping string
+     * @return a list of EndpointInformation objects representing the extracted endpoint information
+     */
+    private static List<EndpointInformation> extractAnnotationPathValues(ClassOrInterfaceDeclaration javaClass, Set<String> httpMethodClasses, String classRequestMappingString) {
+        return javaClass.getMethods().stream()
+                .flatMap(method -> method.getAnnotations().stream().filter(annotation -> httpMethodClasses.contains(annotation.getNameAsString()))
+                        .flatMap(annotation -> extractPathsFromAnnotation(annotation).stream()
+                                .map(path -> new EndpointInformation(classRequestMappingString, method.getNameAsString(), annotation.getNameAsString(), path,
+                                        javaClass.getNameAsString(), method.getBegin().get().line, method.getAnnotations().stream().map(AnnotationExpr::toString).toList()))))
+                .toList();
+    }
+
+    /**
+     * Extracts the paths from the given annotation.
+     *
+     * This method processes the provided annotation to extract path values.
+     * It handles both single-member and normal annotations, extracting the
+     * path values from the annotation's member values or pairs.
+     *
+     * @param annotation the annotation to extract paths from
+     * @return a list of extracted path values
+     */
+    private static List<String> extractPathsFromAnnotation(AnnotationExpr annotation) {
+        List<String> paths = new ArrayList<>();
+        if (annotation instanceof SingleMemberAnnotationExpr singleMemberAnnotationExpr) {
+            Expression memberValue = singleMemberAnnotationExpr.getMemberValue();
+            if (memberValue instanceof ArrayInitializerExpr arrayInitializerExpr) {
+                paths.addAll(arrayInitializerExpr.getValues().stream().map(Expression::toString).collect(Collectors.toList()));
+            }
+            else {
+                paths.add(memberValue.toString());
+            }
+        }
+        else if (annotation instanceof NormalAnnotationExpr normalAnnotationExpr) {
+            normalAnnotationExpr.getPairs().stream().filter(pair -> "value".equals(pair.getNameAsString())).forEach(pair -> paths.add(pair.getValue().toString()));
+        }
+        return paths;
+    }
+
+    /**
+     * Extracts the class-level request mapping from a given class declaration.
+     *
+     * This method scans the annotations of the provided class to find a `RequestMapping` annotation.
+     * It then checks if the class contains any methods annotated with HTTP method annotations.
+     * If such methods are found, it extracts the value of the `RequestMapping` annotation.
+     *
+     * @param javaClass         the class declaration to extract the request mapping from
+     * @param httpMethodClasses a set of HTTP method annotation class names
+     * @return the extracted request mapping value, or an empty string if no request mapping is found or the class has no HTTP method annotations
+     */
+    private static String extractClassRequestMapping(ClassOrInterfaceDeclaration javaClass, Set<String> httpMethodClasses) {
+        boolean hasEndpoint = javaClass.getMethods().stream().flatMap(method -> method.getAnnotations().stream())
+                .anyMatch(annotation -> httpMethodClasses.contains(annotation.getNameAsString()));
+
+        if (!hasEndpoint) {
+            return "";
+        }
+
+        String classRequestMapping = javaClass.getAnnotations().stream().filter(annotation -> annotation.getNameAsString().equals(RequestMapping.class.getSimpleName())).findFirst()
+                .map(annotation -> {
+                    if (annotation instanceof SingleMemberAnnotationExpr singleMemberAnnotationExpr) {
+                        return singleMemberAnnotationExpr.getMemberValue().toString();
+                    }
+                    else if (annotation instanceof NormalAnnotationExpr normalAnnotationExpr) {
+                        return normalAnnotationExpr.getPairs().stream().filter(pair -> "path".equals(pair.getNameAsString())).map(pair -> pair.getValue().toString()).findFirst()
+                                .orElse("");
+                    }
+                    return "";
+                }).orElse("");
+
+        return classRequestMapping;
+    }
+
+    /**
+     * Prints the list of files that failed to parse.
+     *
+     * This method checks if the provided list of file paths is not empty.
+     * If it is not empty, it prints a message indicating that some files failed to parse,
+     * followed by the paths of the files that failed.
+     *
+     * @param filesFailedToParse the list of file paths that failed to parse
+     */
+    private static void printFilesFailedToParse(List<String> filesFailedToParse) {
+        if (!filesFailedToParse.isEmpty()) {
+            logger.warn("Files failed to parse:", filesFailedToParse);
+            for (String file : filesFailedToParse) {
+                logger.warn(file);
+            }
+        }
+    }
+
+    /**
+     * Writes the list of endpoint class information to a JSON file.
+     *
+     * This method uses the Jackson ObjectMapper to serialize the list of
+     * EndpointClassInformation objects and write them to a file specified
+     * by the EndpointParsingResultPath constant.
+     *
+     * @param endpointClasses the list of EndpointClassInformation objects to write to the file
+     */
+    private static void writeEndpointsToFile(List<EndpointClassInformation> endpointClasses) {
+        try {
+            new ObjectMapper().writeValue(new File(ENDPOINT_PARSING_RESULT_PATH), endpointClasses);
+        }
+        catch (IOException e) {
+            logger.error("Failed to write endpoint information to file", e);
+        }
+    }
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalysis.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalysis.java
new file mode 100644
index 000000000000..57b1d2dfc289
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalysis.java
@@ -0,0 +1,6 @@
+package de.tum.cit.endpointanalysis;
+
+import java.util.List;
+
+public record RestCallAnalysis(List<RestCallWithMatchingEndpoint> restCallsWithMatchingEndpoints, List<RestCallInformation> restCallsWithoutMatchingEndpoints) {
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalyzer.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalyzer.java
new file mode 100644
index 000000000000..aac71d6573bf
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallAnalyzer.java
@@ -0,0 +1,144 @@
+package de.tum.cit.endpointanalysis;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class RestCallAnalyzer {
+
+    private static final String REST_CALL_ANALYSIS_RESULT_PATH = "restCallAnalysisResult.json";
+
+    private static final Logger logger = LoggerFactory.getLogger(RestCallAnalyzer.class);
+
+    public static void main(String[] args) {
+        analyzeRestCalls();
+        printRestCallAnalysisResult();
+    }
+
+    /**
+     * The RestCallAnalyzer analyzes the client REST Calls and focuses on them having a matching Endpoint on the server
+     *
+     * This method reads endpoint and REST call information from JSON files.
+     * It then matches the REST calls with the endpoints they are calling and
+     * writes the analysis result to a JSON file.
+     * REST calls without matching endpoints are also recorded.
+     */
+    private static void analyzeRestCalls() {
+        ObjectMapper mapper = new ObjectMapper();
+
+        try {
+            List<EndpointClassInformation> endpointClasses = mapper.readValue(new File(EndpointParser.ENDPOINT_PARSING_RESULT_PATH),
+                    new TypeReference<List<EndpointClassInformation>>() {
+                    });
+            List<RestCallFileInformation> restCalls = mapper.readValue(new File(EndpointParser.REST_CALL_PARSING_RESULT_PATH), new TypeReference<List<RestCallFileInformation>>() {
+            });
+
+            List<RestCallWithMatchingEndpoint> restCallsWithMatchingEndpoint = new ArrayList<>();
+            List<RestCallInformation> restCallsWithoutMatchingEndpoint = new ArrayList<>();
+
+            Map<String, List<EndpointInformation>> endpointMap = new HashMap<>();
+
+            // Populate the map with endpoints
+            for (EndpointClassInformation endpointClass : endpointClasses) {
+                for (EndpointInformation endpoint : endpointClass.endpoints()) {
+                    String endpointURI = endpoint.buildComparableEndpointUri();
+                    endpointMap.computeIfAbsent(endpointURI, uri -> new ArrayList<>()).add(endpoint);
+                }
+            }
+
+            for (RestCallFileInformation restCallFile : restCalls) {
+                for (RestCallInformation restCall : restCallFile.restCalls()) {
+                    String restCallURI = restCall.buildComparableRestCallUri();
+                    List<EndpointInformation> matchingEndpoints = endpointMap.getOrDefault(restCallURI, new ArrayList<>());
+
+                    checkForWildcardMatches(restCall, matchingEndpoints, restCallURI, endpointMap);
+
+                    if (matchingEndpoints.isEmpty()) {
+                        restCallsWithoutMatchingEndpoint.add(restCall);
+                    }
+                    else {
+                        for (EndpointInformation endpoint : matchingEndpoints) {
+                            restCallsWithMatchingEndpoint.add(new RestCallWithMatchingEndpoint(endpoint, restCall, restCall.fileName()));
+                        }
+                    }
+                }
+            }
+
+            RestCallAnalysis restCallAnalysis = new RestCallAnalysis(restCallsWithMatchingEndpoint, restCallsWithoutMatchingEndpoint);
+            mapper.writeValue(new File(REST_CALL_ANALYSIS_RESULT_PATH), restCallAnalysis);
+        }
+        catch (IOException e) {
+            logger.error("Failed to analyze REST calls", e);
+        }
+    }
+
+    /**
+     * Checks for wildcard matches and adds matching endpoints to the list.
+     *
+     * This method is used to find matching endpoints for REST calls that use wildcard URIs.
+     * If no exact match is found for a REST call, it checks if the REST call URI ends with a wildcard ('*').
+     * It then iterates through the endpoint map to find URIs that start with the same prefix as the REST call URI
+     * (excluding the wildcard) and have the same HTTP method. If such URIs are found, they are added to the list of matching endpoints.
+     *
+     * @param restCall          The REST call information to check for wildcard matches.
+     * @param matchingEndpoints The list of matching endpoints to be populated.
+     * @param restCallURI       The URI of the REST call being checked.
+     * @param endpointMap       The map of endpoint URIs to their corresponding information.
+     */
+    private static void checkForWildcardMatches(RestCallInformation restCall, List<EndpointInformation> matchingEndpoints, String restCallURI,
+            Map<String, List<EndpointInformation>> endpointMap) {
+        if (matchingEndpoints.isEmpty() && restCallURI.endsWith("*")) {
+            for (String uri : endpointMap.keySet()) {
+                if (uri.startsWith(restCallURI.substring(0, restCallURI.length() - 1))
+                        && endpointMap.get(uri).get(0).getHttpMethod().toLowerCase().equals(restCall.method().toLowerCase())) {
+                    matchingEndpoints.addAll(endpointMap.get(uri));
+                }
+            }
+        }
+    }
+
+    /**
+     * Prints the endpoint analysis result.
+     *
+     * This method reads the endpoint analysis result from a JSON file and prints
+     * the details of unused endpoints to the console. The details include the
+     * endpoint URI, HTTP method, file path, and line number. If no matching REST
+     * call is found for an endpoint, it prints a message indicating this.
+     */
+    private static void printRestCallAnalysisResult() {
+        ObjectMapper mapper = new ObjectMapper();
+
+        RestCallAnalysis restCallsAndMatchingEndpoints = null;
+
+        try {
+            restCallsAndMatchingEndpoints = mapper.readValue(new File(REST_CALL_ANALYSIS_RESULT_PATH), new TypeReference<RestCallAnalysis>() {
+            });
+        }
+        catch (IOException e) {
+            logger.error("Failed to deserialize rest call analysis results", e);
+        }
+
+        restCallsAndMatchingEndpoints.restCallsWithoutMatchingEndpoints().stream().forEach(endpoint -> {
+            logger.info("=============================================");
+            logger.info("REST call URI: {}", endpoint.buildCompleteRestCallURI());
+            logger.info("HTTP method: {}", endpoint.method());
+            logger.info("File path: {}", endpoint.fileName());
+            logger.info("Line: {}", endpoint.line());
+            logger.info("=============================================");
+            logger.info("No matching endpoint found for REST call: {}", endpoint.buildCompleteRestCallURI());
+            logger.info("---------------------------------------------");
+            logger.info("");
+        });
+
+        logger.info("Number of REST calls without matching endpoints: {}", restCallsAndMatchingEndpoints.restCallsWithoutMatchingEndpoints().size());
+    }
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallFileInformation.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallFileInformation.java
new file mode 100644
index 000000000000..847ec03b1561
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallFileInformation.java
@@ -0,0 +1,4 @@
+package de.tum.cit.endpointanalysis;
+
+public record RestCallFileInformation(String fileName, RestCallInformation[] restCalls) {
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallInformation.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallInformation.java
new file mode 100644
index 000000000000..fb1e44f92f2a
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallInformation.java
@@ -0,0 +1,18 @@
+package de.tum.cit.endpointanalysis;
+
+public record RestCallInformation(String method, String url, int line, String fileName) {
+
+    public String buildCompleteRestCallURI() {
+        return this.url.replace("`", "");
+    }
+
+    public String buildComparableRestCallUri() {
+        // Replace arguments with placeholder
+        String result = this.buildCompleteRestCallURI().replaceAll("\\$\\{.*?\\}", ":param:");
+
+        // Remove query parameters
+        result = result.split("\\?")[0];
+
+        return result;
+    }
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallWithMatchingEndpoint.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallWithMatchingEndpoint.java
new file mode 100644
index 000000000000..0b3ebed9d527
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/RestCallWithMatchingEndpoint.java
@@ -0,0 +1,4 @@
+package de.tum.cit.endpointanalysis;
+
+public record RestCallWithMatchingEndpoint(EndpointInformation matchingEndpoint, RestCallInformation restCallInformation, String filePath) {
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/UsedEndpoints.java b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/UsedEndpoints.java
new file mode 100644
index 000000000000..afdb8fa06846
--- /dev/null
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/java/de/tum/cit/endpointanalysis/UsedEndpoints.java
@@ -0,0 +1,6 @@
+package de.tum.cit.endpointanalysis;
+
+import java.util.List;
+
+public record UsedEndpoints(EndpointInformation endpointInformation, List<RestCallInformation> matchingRestCalls, String filePath) {
+}
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/AnalysisOfEndpointConnectionsClient.ts b/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/AnalysisOfEndpointConnectionsClient.ts
index f774acfdc92b..ad4f19a9322e 100644
--- a/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/AnalysisOfEndpointConnectionsClient.ts
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/AnalysisOfEndpointConnectionsClient.ts
@@ -1,8 +1,9 @@
-import { readdirSync } from 'node:fs';
+import { readdirSync, readFileSync } from 'node:fs';
 import { join, resolve } from 'node:path';
 import { Preprocessor } from './Preprocessor';
 import { Postprocessor } from './Postprocessor';
 import { writeFileSync } from 'node:fs';
+import { parse, TSESTree } from '@typescript-eslint/typescript-estree';
 
 /**
  * Recursively collects all TypeScript files in a directory.
@@ -26,20 +27,58 @@ function collectTypeScriptFiles(dir: string, files: string[] = []) : string[] {
     return files;
 }
 
+/**
+ * Parses a TypeScript file and returns its Abstract Syntax Tree (AST).
+ *
+ * @param filePath - The path to the TypeScript file to be parsed.
+ * @returns The TSESTree of the parsed TypeScript file.
+ */
+function parseTypeScriptFile(filePath: string): TSESTree.Program | null {
+    const code = readFileSync(filePath, 'utf8');
+    try {
+        return parse(code, {
+            loc: true,
+            comment: true,
+            tokens: true,
+            ecmaVersion: 2020,
+            sourceType: 'module',
+        });
+    } catch (error) {
+        console.error(`Failed to parse TypeScript file at ${filePath}:`, error);
+        console.error('Please make sure the file is valid TypeScript code.');
+        return null;
+    }
+}
+
 const clientDirPath = resolve('src/main/webapp/app');
 
 const tsFiles = collectTypeScriptFiles(clientDirPath);
 
-// preprocess each file
+// create and store Syntax Tree for each file
+const astMap = new Map<string, TSESTree.Program>;
 tsFiles.forEach((filePath) => {
-    const preProcessor = new Preprocessor(filePath);
-    preProcessor.preprocessFile();
+    const ast =  parseTypeScriptFile(filePath);
+    if (ast) {
+        astMap.set(filePath, ast);
+    }
+});
+
+// preprocess each file
+Array.from(astMap.keys()).forEach((filePath: string) => {
+    const ast = astMap.get(filePath);
+    if (ast) {
+        const preProcessor = new Preprocessor(ast);
+        preProcessor.preprocessFile();
+    }
 });
 
 // postprocess each file
-tsFiles.forEach((filePath) => {
-    const postProcessor = new Postprocessor(filePath);
-    postProcessor.extractRestCalls();
+Array.from(astMap.keys()).forEach((filePath) => {
+    const ast = astMap.get(filePath);
+    if (ast) {
+        const postProcessor = new Postprocessor(filePath, ast);
+        postProcessor.extractRestCallsFromProgram();
+    }
 });
 
 try {
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Postprocessor.ts b/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Postprocessor.ts
index b90d216a9796..18b54a5f0ac4 100644
--- a/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Postprocessor.ts
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Postprocessor.ts
@@ -38,18 +38,18 @@ class ParsingResult {
 }
 
 export class Postprocessor {
-    static filesWithRestCalls: { filePath: string, restCalls: RestCall[] }[] = [];
+    static filesWithRestCalls: { fileName: string, restCalls: RestCall[] }[] = [];
     private readonly restCalls: RestCall[] = [];
-    private readonly filePath: string;
+    private readonly fileName: string;
     private readonly ast: TSESTree.Program;
 
-    constructor(filePath: string) {
-        this.filePath = filePath;
-        this.ast = Preprocessor.parseTypeScriptFile(Preprocessor.pathPrefix + filePath)
-    }
-
-    extractRestCalls() {
-        this.extractRestCallsFromProgram();
+    /**
+     * @param fileName - The name of the file being processed.
+     * @param ast - The abstract syntax tree (AST) of the processed file.
+     */
+    constructor(fileName: string, ast: TSESTree.Program) {
+        this.fileName = fileName;
+        this.ast = ast;
     }
 
     extractRestCallsFromProgram() {
@@ -61,7 +61,7 @@ export class Postprocessor {
             }
         });
         if (this.restCalls.length > 0) {
-            Postprocessor.filesWithRestCalls.push( {filePath: this.filePath, restCalls: this.restCalls} );
+            Postprocessor.filesWithRestCalls.push( {fileName: this.fileName, restCalls: this.restCalls} );
         }
     }
 
@@ -108,7 +108,7 @@ export class Postprocessor {
                                         urlEvaluationResult = this.evaluateUrl(node.arguments[0], methodDefinition, node, classBody);
                                     }
 
-                                    const fileName = this.filePath;
+                                    const fileName = this.fileName;
                                     if (urlEvaluationResult.resultType === ParsingResultType.EVALUATE_URL_SUCCESS) {
                                         for (let url of urlEvaluationResult.result) {
                                             this.restCalls.push({ method, url, line, fileName });
diff --git a/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Preprocessor.ts b/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Preprocessor.ts
index a90f30fb7a5e..fcdebd828486 100644
--- a/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Preprocessor.ts
+++ b/supporting_scripts/analysis-of-endpoint-connections/src/main/typeScript/Preprocessor.ts
@@ -11,16 +11,16 @@ interface SuperClass {
 interface ChildClass {
     superClass: string;
     name: string;
-    memberVariables: Map<string, MemberVariable>;
+    memberVariables: Map<string, Attribute>;
     parentMethodCalls: ParentMethodCalls[];
 }
 
 interface ParentMethodCalls {
     name: string;
-    parameters: MemberVariable[];
+    parameters: Attribute[];
 }
 
-interface MemberVariable {
+interface Attribute {
     name: string;
     type: string;
     value?: string;
@@ -28,16 +28,16 @@ interface MemberVariable {
 
 export class Preprocessor {
     public static PREPROCESSING_RESULTS = new Map<string, SuperClass>();
-    public static readonly pathPrefix = ''
     private readonly directoryPrefix = 'src/main/webapp/';
-    private readonly fileToPreprocess: string;
     private ast: TSESTree.Program;
 
-    private memberVariables: Map<string, MemberVariable> = new Map<string, MemberVariable>();
+    private memberVariables: Map<string, Attribute> = new Map<string, Attribute>();
 
-    constructor(fileToPreprocess: string) {
-        this.fileToPreprocess = fileToPreprocess;
-        this.ast = Preprocessor.parseTypeScriptFile(Preprocessor.pathPrefix + this.fileToPreprocess);
+    /**
+     * @param ast - The abstract syntax tree (AST) of the processed file.
+     */
+    constructor(ast: TSESTree.Program) {
+        this.ast = ast;
     }
 
     /**
@@ -48,10 +48,6 @@ export class Preprocessor {
      * It also handles named exports that are class declarations.
      */
     preprocessFile() {
-        if (this.ast.type !== 'Program') {
-            return;
-        }
-
         this.ast.body.forEach((node) => {
             if (node.type === 'ClassDeclaration') {
                 this.preprocessClass(node);
@@ -254,11 +250,11 @@ export class Preprocessor {
      * which scans the class body for a property matching the parameter name and returns its value.
      *
      * @param parameterName - The name of the parameter whose value is to be found.
-     * @param filePath - The path to the TypeScript file (relative to the base directory set in `pathPrefix` and `directoryPrefix`) where the parameter value is to be searched.
+     * @param filePath - The path to the TypeScript file (relative to the base directory set in `directoryPrefix`) where the parameter value is to be searched.
      * @returns The value of the parameter if found; otherwise, an empty string.
      */
     findParameterValueByParameterNameAndFilePath (parameterName: string, filePath: string): string {
-        const targetAST = Preprocessor.parseTypeScriptFile(`${Preprocessor.pathPrefix}${this.directoryPrefix}${filePath}.ts`);
+        const targetAST = Preprocessor.parseTypeScriptFile(`${this.directoryPrefix}${filePath}.ts`);
 
         for (const node of targetAST.body) {
             if (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'ClassDeclaration') {