diff --git a/config.json b/config.json
index 7d0aaf9..259007e 100644
--- a/config.json
+++ b/config.json
@@ -1,6 +1,10 @@
{
- "projectDirectory": "../LoopAntiPattern/src",
- "repositoryDirectory": "../LoopAntiPattern/src/main/java/com/example/LoopAntiPattern/data/repository",
- "excludedClasses": ["ProductRepository.java", "ExcludeThat.java"],
+ "projectDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src",
+ "repositoryDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern",
+ "excludedClasses": ["ExcludeThis.java", "ExcludeThat.java"],
"excludedMethods": ["someMethod"]
-}
\ No newline at end of file
+}
+
+//,"ProductRepository.java","ProductService.java"
+//"repositoryDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern/data/repository",
+//"repositoryDirectory": "../fromItestra/LoopAntiPattern/LoopAntiPattern/src/main/java/com/example/LoopAntiPattern/service",
diff --git a/pom.xml b/pom.xml
index c9c62b7..6cb8cee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,26 @@
jgrapht-core
1.5.1
+
+ org.jgrapht
+ jgrapht-io
+ 1.4.0
+
+
+ io.github.classgraph
+ classgraph
+ 4.8.117
+
+
+ org.springframework
+ spring-context
+ 5.3.1
+
+
+ org.springframework
+ spring-tx
+ 5.3.1
+
com.fasterxml.jackson.core
jackson-databind
diff --git a/src/main/java/tum/dpid/AntiPatternDetector.java b/src/main/java/tum/dpid/AntiPatternDetector.java
new file mode 100644
index 0000000..1e37bac
--- /dev/null
+++ b/src/main/java/tum/dpid/AntiPatternDetector.java
@@ -0,0 +1,176 @@
+package tum.dpid;
+
+import org.eclipse.jdt.core.dom.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.*;
+
+/*
+* CompilationUnit
+└── TypeDeclaration (class Example)
+ ├── MethodDeclaration (methodA)
+ │ └── Block
+ │ └── ForStatement
+ │ ├── Expression (int i = 0)
+ │ ├── Expression (i < 10)
+ │ ├── Expression (i++)
+ │ └── Block
+ │ └── MethodInvocation (methodB)
+ ├── MethodDeclaration (methodB)
+ │ └── Block
+ │ └── MethodInvocation (methodC)
+ └── MethodDeclaration (methodC)
+ └── Block
+ └── MethodInvocation (find)
+*/
+
+public class AntiPatternDetector {
+
+ private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find");
+
+ public static void main(String[] args) {
+ String projectDirectoryPath = "../fromItestra/LoopAntiPattern";
+
+ File projectDirectory = new File(projectDirectoryPath);
+ if (!projectDirectory.exists() || !projectDirectory.isDirectory()) {
+ System.out.println("Invalid project directory path.");
+ return;
+ }
+
+ Map methodMap = collectMethods(projectDirectory);
+
+ for (MethodDeclaration method : methodMap.values()) {
+ method.accept(new ForLoopVisitor(methodMap));
+ }
+ }
+
+ private static Map collectMethods(File projectDirectory) {
+ Map methodMap = new HashMap<>();
+ List javaFiles = getJavaFiles(projectDirectory);
+
+ if (javaFiles != null) {
+ for (File javaFile : javaFiles) {
+ try {
+ String source = new String(Files.readAllBytes(javaFile.toPath()));
+ ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
+ parser.setSource(source.toCharArray());
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+
+ CompilationUnit cu = (CompilationUnit) parser.createAST(null);
+ cu.accept(new MethodCollector(methodMap));
+ } catch (IOException e) {
+ System.err.println("Error reading file: " + javaFile.getName());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return methodMap;
+ }
+
+ private static List getJavaFiles(File directory) {
+ List javaFiles = new ArrayList<>();
+ File[] files = directory.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ javaFiles.addAll(getJavaFiles(file));
+ } else if (file.isFile() && file.getName().endsWith(".java")) {
+ javaFiles.add(file);
+ }
+ }
+ }
+ return javaFiles;
+ }
+
+ private static class MethodCollector extends ASTVisitor {
+ private final Map methodMap;
+
+ public MethodCollector(Map methodMap) {
+ this.methodMap = methodMap;
+ }
+
+ @Override
+ public boolean visit(TypeDeclaration node) {
+ for (MethodDeclaration method : node.getMethods()) {
+ String methodName = method.getName().getIdentifier();
+ methodMap.put(methodName, method);
+ }
+ return super.visit(node);
+ }
+ }
+
+ private static class ForLoopVisitor extends ASTVisitor {
+ private final Map methodMap;
+
+ public ForLoopVisitor(Map methodMap) {
+ this.methodMap = methodMap;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ node.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(org.eclipse.jdt.core.dom.ForStatement forStatement) {
+ forStatement.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(MethodInvocation methodInvocation) {
+ String methodName = methodInvocation.getName().getIdentifier();
+ if (DB_METHODS.contains(methodName)) {
+ reportAntiPattern(methodInvocation);
+ } else if (methodMap.containsKey(methodName)) {
+ MethodDeclaration methodDeclaration = methodMap.get(methodName);
+ Set visitedMethods = new HashSet<>();
+ visitedMethods.add(node.getName().getIdentifier());
+ methodDeclaration.accept(new MethodInvocationVisitor(methodMap, methodInvocation, visitedMethods));
+ }
+ return super.visit(methodInvocation);
+ }
+ });
+ return super.visit(forStatement);
+ }
+ });
+ return super.visit(node);
+ }
+
+ private void reportAntiPattern(MethodInvocation methodInvocation) {
+ System.out.println("Anti-pattern detected: " + methodInvocation.getName().getIdentifier() +
+ " call inside a for loop at line " +
+ ((CompilationUnit) methodInvocation.getRoot()).getLineNumber(methodInvocation.getStartPosition()));
+ }
+ }
+
+ private static class MethodInvocationVisitor extends ASTVisitor {
+ private final Map methodMap;
+ private final MethodInvocation originalInvocation;
+ private final Set visitedMethods;
+
+ public MethodInvocationVisitor(Map methodMap, MethodInvocation originalInvocation, Set visitedMethods) {
+ this.methodMap = methodMap;
+ this.originalInvocation = originalInvocation;
+ this.visitedMethods = visitedMethods;
+ }
+
+ @Override
+ public boolean visit(MethodInvocation methodInvocation) {
+ String methodName = methodInvocation.getName().getIdentifier();
+ if (visitedMethods.contains(methodName)) {
+ return false; // Skip already visited methods in this call chain
+ }
+ visitedMethods.add(methodName);
+
+ if (DB_METHODS.contains(methodName)) {
+ System.out.println("Anti-pattern detected: " + originalInvocation.getName().getIdentifier() +
+ " indirectly calls " + methodInvocation.getName().getIdentifier() +
+ " inside a for loop at line " +
+ ((CompilationUnit) originalInvocation.getRoot()).getLineNumber(originalInvocation.getStartPosition()));
+ } else if (methodMap.containsKey(methodName)) {
+ MethodDeclaration methodDeclaration = methodMap.get(methodName);
+ methodDeclaration.accept(new MethodInvocationVisitor(methodMap, originalInvocation, visitedMethods));
+ }
+ return super.visit(methodInvocation);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/tum/dpid/CallChainAnalyzer.java b/src/main/java/tum/dpid/CallChainAnalyzer.java
index 124ed28..20ca3bc 100644
--- a/src/main/java/tum/dpid/CallChainAnalyzer.java
+++ b/src/main/java/tum/dpid/CallChainAnalyzer.java
@@ -13,24 +13,26 @@
public class CallChainAnalyzer {
+ private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find",
+ "selectById", "selectExistingById", "delete",
+ "selectByArticleNumber", "saveWithoutFlush", "merge", "selectFrom", "callSave");
+
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
AnalyzerConfig config;
try {
- //config file path will be read as a cmd line argument after building this tool as a jar application
config = mapper.readValue(new File("config.json"), AnalyzerConfig.class);
} catch (IOException e) {
System.out.println("Error reading config file:\n" + e.getMessage());
System.exit(1);
- return; //This is added to prevent "variable might not be initialized" warnings. This line is unreachable
+ return;
}
- // find and collect methods under repository folder to create a target list
String directoryPath = config.getRepositoryDirectory();
Path dirPath = Paths.get(directoryPath);
- List DB_METHODS = new ArrayList<>();
+ List dbMethods = new ArrayList<>(DB_METHODS);
try {
List javaFiles = Files.walk(dirPath)
@@ -39,7 +41,7 @@ public static void main(String[] args) throws IOException {
.toList();
for (Path javaFile : javaFiles) {
- DB_METHODS.addAll(extractMethodNames(javaFile, config.getExcludedClasses(), config.getExcludedMethods()));
+ dbMethods.addAll(extractMethodNames(javaFile, config.getExcludedClasses(), config.getExcludedMethods()));
}
} catch (IOException e) {
e.printStackTrace();
@@ -49,31 +51,24 @@ public static void main(String[] args) throws IOException {
File projectDirectory = new File(projectDirectoryPath);
if (!projectDirectory.exists() || !projectDirectory.isDirectory()) {
- System.out.println("Invalid project directory path.");
+ System.err.println("[ERROR] Invalid project directory path.");
return;
}
- Map methodMap = collectMethods(projectDirectory, config.getExcludedMethods());
- Map> callGraph = buildCallGraph(methodMap);
+ Map methodMap = collectMethods(projectDirectory, config.getExcludedClasses(), config.getExcludedMethods());
+ Map> callGraph = buildCallGraph(methodMap);
- for (String targetMethod : DB_METHODS) {
- List callChain = new ArrayList<>();
- traceCallChainInOrder(targetMethod, callGraph, new HashSet<>(), callChain, methodMap);
-
- if (callChain.isEmpty()) {
- System.out.println("No calls found for method: " + targetMethod);
- }
-
- System.out.println("Call graph for method: " + targetMethod);
+ for (String targetMethod : dbMethods) {
+ System.out.println("\nCall graph for method: " + targetMethod);
drawCallGraph(targetMethod, callGraph, 0, new HashSet<>());
- System.out.println();
-
}
+
+ checkForDatabaseCallsInLoops(methodMap, callGraph);
}
private static List extractMethodNames(Path javaFilePath, List excludedClasses, List excludedMethods) throws IOException {
String className = javaFilePath.getFileName().toString();
- if(excludedClasses.contains(className)) {
+ if (excludedClasses.contains(className)) {
return Collections.emptyList();
}
String content = new String(Files.readAllBytes(javaFilePath));
@@ -84,45 +79,31 @@ private static List extractMethodNames(Path javaFilePath, List e
final CompilationUnit cu = (CompilationUnit) parser.createAST(null);
MethodNameVisitor visitor = new MethodNameVisitor(excludedMethods);
cu.accept(visitor);
+ System.out.println("New db methods found" + visitor.getMethodNames());
return visitor.getMethodNames();
}
- static class MethodNameVisitor extends ASTVisitor {
- private final List methodNames = new ArrayList<>();
- private final List excludedMethods;
-
- public MethodNameVisitor(List excludedMethods) {
- this.excludedMethods = excludedMethods;
- }
- @Override
- public boolean visit(MethodDeclaration node) {
- if(!excludedMethods.contains(node.getName().getIdentifier())){
- methodNames.add(node.getName().getIdentifier());
- }
- return super.visit(node);
- }
-
- public List getMethodNames() {
- return methodNames;
- }
- }
-
- private static Map collectMethods(File projectDirectory, List excludedMethods) throws IOException {
+ private static Map collectMethods(File projectDirectory, List excludedClasses, List excludedMethods) {
Map methodMap = new HashMap<>();
List javaFiles = getJavaFiles(projectDirectory);
- for (File javaFile : javaFiles) {
- try {
- String source = new String(Files.readAllBytes(javaFile.toPath()));
- ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
- parser.setSource(source.toCharArray());
- parser.setKind(ASTParser.K_COMPILATION_UNIT);
-
- CompilationUnit cu = (CompilationUnit) parser.createAST(null);
- cu.accept(new MethodCollector(methodMap, excludedMethods));
- } catch (IOException e) {
- System.err.println("Error reading file: " + javaFile.getName());
- e.printStackTrace();
+ if (javaFiles != null) {
+ for (File javaFile : javaFiles) {
+ String className = javaFile.getName();
+ if (!excludedClasses.contains(className)) {
+ try {
+ String source = new String(Files.readAllBytes(javaFile.toPath()));
+ ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
+ parser.setSource(source.toCharArray());
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+
+ CompilationUnit cu = (CompilationUnit) parser.createAST(null);
+ cu.accept(new MethodCollector(methodMap, excludedMethods));
+ } catch (IOException e) {
+ System.err.println("[ERROR] Error reading file: " + javaFile.getName());
+ e.printStackTrace();
+ }
+ }
}
}
@@ -146,76 +127,143 @@ private static List getJavaFiles(File directory) {
return javaFiles;
}
- private static Map> buildCallGraph(Map methodMap) {
- Map> callGraph = new HashMap<>();
+ private static Map> buildCallGraph(Map methodMap) {
+ Map> callGraph = new HashMap<>();
for (MethodDeclaration method : methodMap.values()) {
- if(method.getName().toString().equals("updateProducts"))
- {
- System.out.println("");
- }
method.accept(new ASTVisitor() {
@Override
public boolean visit(MethodInvocation node) {
String caller = method.getName().toString();
String callee = node.getName().toString();
- callGraph.computeIfAbsent(callee, k -> new ArrayList<>()).add(caller);
+ callGraph.computeIfAbsent(callee, k -> new HashSet<>()).add(caller);
+ return super.visit(node);
+ }
+ });
+ }
+
+ return callGraph;
+ }
+
+ private static void drawCallGraph(String methodName, Map> callGraph, int level, Set visited) {
+ if (!visited.add(methodName)) {
+ return;
+ }
+ printIndented(methodName, level);
+ Set callers = callGraph.get(methodName);
+ if (callers != null) {
+ for (String caller : callers) {
+ drawCallGraph(caller, callGraph, level + 1, visited);
+ }
+ }
+ }
+
+ private static void printIndented(String methodName, int level) {
+ for (int i = 0; i < level; i++) {
+ System.out.print(" ");
+ }
+ System.out.println(methodName);
+ }
+
+ private static void checkForDatabaseCallsInLoops(Map methodMap, Map> callGraph) {
+ for (MethodDeclaration method : methodMap.values()) {
+ method.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(ForStatement node) {
+ checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph);
+ return super.visit(node);
+ }
+
+ @Override
+ public boolean visit(WhileStatement node) {
+ checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph);
+ return super.visit(node);
+ }
+
+ @Override
+ public boolean visit(DoStatement node) {
+ checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph);
+ return super.visit(node);
+ }
+ @Override
+ public boolean visit(EnhancedForStatement node) {
+ checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph);
return super.visit(node);
}
@Override
public boolean visit(LambdaExpression node) {
- node.getBody().accept(new ASTVisitor() {
- @Override
- public boolean visit(MethodInvocation lambdaNode) {
- String caller = method.getName().toString();
- String callee = lambdaNode.getName().toString();
- callGraph.computeIfAbsent(callee, k -> new ArrayList<>()).add(caller);
- return super.visit(lambdaNode);
- }
- });
+ checkMethodInvocationsInLoop(node.getBody(), methodMap, callGraph);
+ return super.visit(node);
+ }
+ @Override
+ public boolean visit(MethodInvocation node) {
+ // Check for stream method calls like map, filter, etc.
+ if (node.getName().getIdentifier().equals("map") || node.getName().getIdentifier().equals("forEach") || node.getName().getIdentifier().equals("stream")) {
+ node.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(LambdaExpression lambdaExpression) {
+ checkMethodInvocationsInLoop(lambdaExpression.getBody(), methodMap, callGraph);
+ return super.visit(lambdaExpression);
+ }
+
+ @Override
+ public boolean visit(MethodInvocation innerNode) {
+ String methodName = innerNode.getName().getIdentifier();
+ if (DB_METHODS.contains(methodName)) {
+ reportAntiPattern(innerNode);
+ } else if (methodMap.containsKey(methodName)) {
+ traceMethodCallsInLoop(methodName, methodMap, callGraph, new HashSet<>(), innerNode);
+ }
+ return super.visit(innerNode);
+ }
+ });
+ }
return super.visit(node);
}
});
}
+ }
- return callGraph;
+ private static void checkMethodInvocationsInLoop(ASTNode loopBody, Map methodMap, Map> callGraph) {
+ loopBody.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(MethodInvocation node) {
+ String methodName = node.getName().getIdentifier();
+ if (DB_METHODS.contains(methodName)) {
+ reportAntiPattern(node);
+ } else if (methodMap.containsKey(methodName)) {
+ traceMethodCallsInLoop(methodName, methodMap, callGraph, new HashSet<>(), node);
+ }
+ return super.visit(node);
+ }
+ });
}
- private static void traceCallChainInOrder(String methodName, Map> callGraph, Set visited, List callChain, Map methodMap) {
+ private static void traceMethodCallsInLoop(String methodName, Map methodMap, Map> callGraph, Set visited, MethodInvocation originalInvocation) {
if (!visited.add(methodName)) {
return;
}
- callChain.add(methodName);
- List callees = callGraph.get(methodName);
+ Set callees = callGraph.get(methodName);
if (callees != null) {
for (String callee : callees) {
- traceCallChainInOrder(callee, callGraph, visited, callChain, methodMap);
+ if (DB_METHODS.contains(callee)) {
+ reportAntiPattern(originalInvocation);
+ } else if (methodMap.containsKey(callee)) {
+ traceMethodCallsInLoop(callee, methodMap, callGraph, visited, originalInvocation);
+ }
}
}
}
- private static void drawCallGraph(String methodName, Map> callGraph, int level, Set visited) {
- if (!visited.add(methodName)) {
- return;
- }
- printIndented(methodName, level);
- List callers = callGraph.get(methodName);
- if (callers != null) {
- for (String caller : callers) {
- drawCallGraph(caller, callGraph, level + 1, visited);
- }
- }
- }
+ private static void reportAntiPattern(MethodInvocation methodInvocation) {
+ System.err.println("[ANTI-PATTERN] Detected: " + methodInvocation.getName().getIdentifier() +
+ " call inside a loop at line " +
+ ((CompilationUnit) methodInvocation.getRoot()).getLineNumber(methodInvocation.getStartPosition()));
- private static void printIndented(String methodName, int level) {
- for (int i = 0; i < level; i++) {
- System.out.print("---- ");
- }
- System.out.println(methodName);
}
static class MethodCollector extends ASTVisitor {
@@ -229,10 +277,33 @@ static class MethodCollector extends ASTVisitor {
@Override
public boolean visit(MethodDeclaration node) {
- if(!excludedMethods.contains(node.getName().toString())) {
+
+ if (!excludedMethods.contains(node.getName().toString())) {
methodMap.put(node.getName().toString(), node);
}
return super.visit(node);
}
}
+
+ static class MethodNameVisitor extends ASTVisitor {
+ private final List methodNames = new ArrayList<>();
+ private final List excludedMethods;
+
+ public MethodNameVisitor(List excludedMethods) {
+ this.excludedMethods = excludedMethods;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ if (!excludedMethods.contains(node.getName().getIdentifier())) {
+ methodNames.add(node.getName().getIdentifier());
+ }
+ return super.visit(node);
+ }
+
+ public List getMethodNames() {
+ return methodNames;
+ }
+ }
+
}
diff --git a/src/main/java/tum/dpid/DynamicCallChainAnalyzer.java b/src/main/java/tum/dpid/DynamicCallChainAnalyzer.java
new file mode 100644
index 0000000..d2988f3
--- /dev/null
+++ b/src/main/java/tum/dpid/DynamicCallChainAnalyzer.java
@@ -0,0 +1,212 @@
+package com.itestra.callchain;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.*;
+
+import org.eclipse.jdt.core.dom.*;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+import tum.dpid.CallChainAnalyzer;
+
+public class DynamicCallChainAnalyzer {
+
+ private static List dbMethods;
+
+ public static void main(String[] args) {
+ DynamicCallChainAnalyzer analyzer = new DynamicCallChainAnalyzer();
+ analyzer.initializeDbMethods();
+ String projectDirectoryPath = "../fromItestra/LoopAntiPattern";
+
+ File projectDirectory = new File(projectDirectoryPath);
+ if (!projectDirectory.exists() || !projectDirectory.isDirectory()) {
+ System.out.println("Invalid project directory path.");
+ return;
+ }
+
+ Map methodMap = collectMethods(projectDirectory);
+ Map> callGraph = buildCallGraph(methodMap);
+
+ for (String dbMethod : dbMethods) {
+ System.out.println("Finding call chains for database method: " + dbMethod);
+ Set visited = new HashSet<>();
+ List callChain = new ArrayList<>();
+ traceCallChainsToMethod(dbMethod, callGraph, new HashSet<>(), callChain, methodMap);
+ }
+ }
+
+ public void initializeDbMethods() {
+ dbMethods = new ArrayList<>();
+ // Add common database methods by naming convention
+ addCommonDbMethods();
+
+ // Scan classes in the project
+ String packageName = "com.itestra"; // Specify your project's base package
+ List> classes = getClasses(packageName);
+
+ for (Class> clazz : classes) {
+ // Check if class is a repository
+ if (clazz.isAnnotationPresent(Repository.class)) {
+ scanClassForDbMethods(clazz);
+ }
+
+ // Check if methods are transactional
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(Transactional.class)) {
+ dbMethods.add(method.getName());
+ }
+ }
+ }
+ }
+
+ private void addCommonDbMethods() {
+ dbMethods.add("flush");
+ dbMethods.add("save");
+ dbMethods.add("update");
+ dbMethods.add("persist");
+ dbMethods.add("remove");
+ dbMethods.add("find");
+ dbMethods.add("selectById");
+ dbMethods.add("selectExistingById");
+ dbMethods.add("delete");
+ dbMethods.add("selectByArticleNumber");
+ dbMethods.add("saveWithoutFlush");
+ dbMethods.add("merge");
+ dbMethods.add("selectFrom");
+ }
+
+ private void scanClassForDbMethods(Class> clazz) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (isDatabaseMethod(method)) {
+ dbMethods.add(method.getName());
+ }
+ }
+ }
+
+ private boolean isDatabaseMethod(Method method) {
+ String methodName = method.getName().toLowerCase();
+ // Check common database operation prefixes
+ return methodName.startsWith("save") ||
+ methodName.startsWith("find") ||
+ methodName.startsWith("delete") ||
+ methodName.startsWith("update") ||
+ methodName.startsWith("select") ||
+ methodName.startsWith("remove") ||
+ methodName.startsWith("persist") ||
+ methodName.startsWith("flush") ||
+ methodName.startsWith("merge");
+ }
+
+ private List> getClasses(String packageName) {
+ // Use ClassGraph to scan and retrieve all classes in the package
+ List> classes = new ArrayList<>();
+ try (ScanResult scanResult = new ClassGraph().acceptPackages(packageName).scan()) {
+ for (ClassInfo classInfo : scanResult.getAllClasses()) {
+ classes.add(Class.forName(classInfo.getName()));
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ return classes;
+ }
+
+ public List getDbMethods() {
+ return dbMethods;
+ }
+
+ private static Map collectMethods(File projectDirectory) {
+ Map methodMap = new HashMap<>();
+ List javaFiles = getJavaFiles(projectDirectory);
+
+ if (javaFiles != null) {
+ for (File javaFile : javaFiles) {
+ try {
+ String source = new String(Files.readAllBytes(javaFile.toPath()));
+ ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
+ parser.setSource(source.toCharArray());
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+
+ CompilationUnit cu = (CompilationUnit) parser.createAST(null);
+ cu.accept(new MethodCollector(methodMap));
+ } catch (IOException e) {
+ System.err.println("Error reading file: " + javaFile.getName());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return methodMap;
+ }
+
+ private static List getJavaFiles(File directory) {
+ List javaFiles = new ArrayList<>();
+ File[] files = directory.listFiles();
+
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ javaFiles.addAll(getJavaFiles(file));
+ } else if (file.getName().endsWith(".java")) {
+ javaFiles.add(file);
+ }
+ }
+ }
+
+ return javaFiles;
+ }
+
+ private static Map> buildCallGraph(Map methodMap) {
+ Map> callGraph = new HashMap<>();
+
+ for (MethodDeclaration method : methodMap.values()) {
+ method.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(MethodInvocation node) {
+ String caller = method.getName().toString();
+ String callee = node.getName().toString();
+ callGraph.computeIfAbsent(caller, k -> new HashSet<>()).add(callee);
+ return super.visit(node);
+ }
+ });
+ }
+
+ return callGraph;
+ }
+
+ private static void traceCallChainsToMethod(String targetMethod, Map> callGraph, Set visited, List callChain, Map methodMap) {
+ if (!visited.add(targetMethod)) {
+ return;
+ }
+ callChain.add(targetMethod);
+ Set callees = callGraph.get(targetMethod);
+ if (callees != null) {
+ for (String callee : callees) {
+ List newCallChain = new ArrayList<>(callChain);
+ traceCallChainsToMethod(callee, callGraph, visited, newCallChain, methodMap);
+ }
+ } else {
+ if (dbMethods.contains(targetMethod)) {
+ System.out.println("Database method call chain: " + String.join(" -> ", callChain));
+ }
+ }
+ }
+
+ public static class MethodCollector extends ASTVisitor {
+ private final Map methodMap;
+
+ public MethodCollector(Map methodMap) {
+ this.methodMap = methodMap;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ methodMap.put(node.getName().toString(), node);
+ return super.visit(node);
+ }
+ }
+}
diff --git a/src/main/java/tum/dpid/NewCallChainAnalyzer.java b/src/main/java/tum/dpid/NewCallChainAnalyzer.java
new file mode 100644
index 0000000..8d9f35a
--- /dev/null
+++ b/src/main/java/tum/dpid/NewCallChainAnalyzer.java
@@ -0,0 +1,124 @@
+package tum.dpid;
+
+import org.eclipse.jdt.core.dom.*;
+import java.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+public class NewCallChainAnalyzer {
+
+ private static final List DB_METHODS = List.of("flush", "save", "update", "persist", "remove", "find", "selectById", "selectExistingById", "delete", "selectByArticleNumber", "saveWithoutFlush", "merge", "selectFrom");
+
+ public static void main(String[] args) {
+ String projectDirectoryPath = "../fromItestra/LoopAntiPattern";
+
+ File projectDirectory = new File(projectDirectoryPath);
+ if (!projectDirectory.exists() || !projectDirectory.isDirectory()) {
+ System.out.println("Invalid project directory path.");
+ return;
+ }
+
+ Map methodMap = collectMethods(projectDirectory);
+ Map> callGraph = buildCallGraph(methodMap);
+
+ for (String dbMethod : DB_METHODS) {
+ System.out.println("Finding call chains for database method: " + dbMethod);
+ Set visited = new HashSet<>();
+ List callChain = new ArrayList<>();
+ traceCallChainsToMethod(dbMethod, callGraph, new HashSet<>(), callChain, methodMap);
+ }
+ }
+
+ private static Map collectMethods(File projectDirectory) {
+ Map methodMap = new HashMap<>();
+ List javaFiles = getJavaFiles(projectDirectory);
+
+ if (javaFiles != null) {
+ for (File javaFile : javaFiles) {
+ try {
+ String source = new String(Files.readAllBytes(javaFile.toPath()));
+ ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
+ parser.setSource(source.toCharArray());
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+
+ CompilationUnit cu = (CompilationUnit) parser.createAST(null);
+ cu.accept(new MethodCollector(methodMap));
+ } catch (IOException e) {
+ System.err.println("Error reading file: " + javaFile.getName());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return methodMap;
+ }
+
+ private static List getJavaFiles(File directory) {
+ List javaFiles = new ArrayList<>();
+ File[] files = directory.listFiles();
+
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ javaFiles.addAll(getJavaFiles(file));
+ } else if (file.getName().endsWith(".java")) {
+ javaFiles.add(file);
+ }
+ }
+ }
+
+ return javaFiles;
+ }
+
+ private static Map> buildCallGraph(Map methodMap) {
+ Map> callGraph = new HashMap<>();
+
+ for (MethodDeclaration method : methodMap.values()) {
+ method.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(MethodInvocation node) {
+ String caller = method.getName().toString();
+ String callee = node.getName().toString();
+ callGraph.computeIfAbsent(caller, k -> new HashSet<>()).add(callee);
+ return super.visit(node);
+ }
+ });
+ }
+
+ return callGraph;
+ }
+
+ private static void traceCallChainsToMethod(String targetMethod, Map> callGraph, Set visited, List callChain, Map methodMap) {
+ if (!visited.add(targetMethod)) {
+ return;
+ }
+ callChain.add(targetMethod);
+ Set callees = callGraph.get(targetMethod);
+ if (callees != null) {
+ for (String callee : callees) {
+ List newCallChain = new ArrayList<>(callChain);
+ traceCallChainsToMethod(callee, callGraph, visited, newCallChain, methodMap);
+ }
+ } else {
+ if (DB_METHODS.contains(targetMethod)) {
+ System.out.println("Database method call chain: " + String.join(" -> ", callChain));
+ }
+ }
+ }
+
+ static class MethodCollector extends ASTVisitor {
+ private final Map methodMap;
+
+ MethodCollector(Map methodMap) {
+ this.methodMap = methodMap;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ methodMap.put(node.getName().toString(), node);
+ return super.visit(node);
+ }
+ }
+}
+
diff --git a/src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java b/src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java
new file mode 100644
index 0000000..3456ea5
--- /dev/null
+++ b/src/main/java/tum/dpid/OldDynamicCallChainAnalyzer.java
@@ -0,0 +1,253 @@
+package tum.dpid;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.*;
+
+import org.eclipse.jdt.core.dom.*;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+
+public class OldDynamicCallChainAnalyzer {
+
+ private static List dbMethods;
+
+ public static void main(String[] args) {
+ OldDynamicCallChainAnalyzer analyzer = new OldDynamicCallChainAnalyzer();
+ analyzer.initializeDbMethods();
+
+ String projectDirectoryPath = "../fromItestra/LoopAntiPattern";
+
+ File projectDirectory = new File(projectDirectoryPath);
+ if (!projectDirectory.exists() || !projectDirectory.isDirectory()) {
+ System.out.println("Invalid project directory path.");
+ return;
+ }
+
+ Map methodMap = collectMethods(projectDirectory);
+ Map> callGraph = buildCallGraph(methodMap);
+
+ for (String dbMethod : dbMethods) {
+ System.out.println("Call chain for method: " + dbMethod);
+ Set visited = new HashSet<>();
+ List callChain = new ArrayList<>();
+ traceCallChainInOrder(dbMethod, callGraph, new HashSet<>(), callChain, methodMap);
+
+ if (callChain.isEmpty()) {
+ System.out.println("No calls found for method: " + dbMethod);
+ } else {
+ for (String method : callChain) {
+ System.out.println(method);
+ }
+ }
+
+ System.out.println("Call graph for method: " + dbMethod);
+ drawCallGraph(dbMethod, callGraph, 0, new HashSet<>());
+ System.out.println();
+ }
+ }
+
+ public void initializeDbMethods() {
+ dbMethods = new ArrayList<>();
+ // Add common database methods by naming convention
+ addCommonDbMethods();
+
+ // Scan classes in the project
+ String packageName = "com.example.LoopAntiPattern"; // Specify your project's base package
+ List> classes = getClasses(packageName);
+
+ for (Class> clazz : classes) {
+ // Check if class is a repository
+ if (clazz.isAnnotationPresent(Repository.class)) {
+ scanClassForDbMethods(clazz);
+ }
+
+ // Check if methods are transactional
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(Transactional.class)) {
+ dbMethods.add(method.getName());
+ }
+ }
+ }
+ }
+
+ private void addCommonDbMethods() {
+ dbMethods.add("flush");
+ dbMethods.add("save");
+ dbMethods.add("update");
+ dbMethods.add("persist");
+ dbMethods.add("remove");
+ dbMethods.add("find");
+ dbMethods.add("selectById");
+ dbMethods.add("selectExistingById");
+ dbMethods.add("delete");
+ dbMethods.add("selectByArticleNumber");
+ dbMethods.add("saveWithoutFlush");
+ dbMethods.add("merge");
+ dbMethods.add("selectFrom");
+ }
+
+ private void scanClassForDbMethods(Class> clazz) {
+ for (Method method : clazz.getDeclaredMethods()) {
+ if (isDatabaseMethod(method)) {
+ dbMethods.add(method.getName());
+ }
+ }
+ }
+
+ private boolean isDatabaseMethod(Method method) {
+ String methodName = method.getName().toLowerCase();
+ // Check common database operation prefixes
+ return methodName.startsWith("save") ||
+ methodName.startsWith("find") ||
+ methodName.startsWith("delete") ||
+ methodName.startsWith("update") ||
+ methodName.startsWith("select") ||
+ methodName.startsWith("remove") ||
+ methodName.startsWith("persist") ||
+ methodName.startsWith("flush") ||
+ methodName.startsWith("merge");
+ }
+
+ private List> getClasses(String packageName) {
+ // Use ClassGraph to scan and retrieve all classes in the package
+ List> classes = new ArrayList<>();
+ try (ScanResult scanResult = new ClassGraph().acceptPackages(packageName).scan()) {
+ for (ClassInfo classInfo : scanResult.getAllClasses()) {
+ classes.add(Class.forName(classInfo.getName()));
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ return classes;
+ }
+
+ private static Map collectMethods(File projectDirectory) {
+ Map methodMap = new HashMap<>();
+ List javaFiles = getJavaFiles(projectDirectory);
+
+ if (javaFiles != null) {
+ for (File javaFile : javaFiles) {
+ try {
+ String source = new String(Files.readAllBytes(javaFile.toPath()));
+ ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
+ parser.setSource(source.toCharArray());
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+
+ CompilationUnit cu = (CompilationUnit) parser.createAST(null);
+ cu.accept(new MethodCollector(methodMap));
+ } catch (IOException e) {
+ System.err.println("Error reading file: " + javaFile.getName());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return methodMap;
+ }
+
+ private static List getJavaFiles(File directory) {
+ List javaFiles = new ArrayList<>();
+ File[] files = directory.listFiles();
+
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ javaFiles.addAll(getJavaFiles(file));
+ } else if (file.getName().endsWith(".java")) {
+ javaFiles.add(file);
+ }
+ }
+ }
+
+ return javaFiles;
+ }
+
+ private static Map> buildCallGraph(Map methodMap) {
+ Map> callGraph = new HashMap<>();
+
+ for (MethodDeclaration method : methodMap.values()) {
+ method.accept(new ASTVisitor() {
+ @Override
+ public boolean visit(MethodInvocation node) {
+ String caller = method.getName().toString();
+ String callee = node.getName().toString();
+ callGraph.computeIfAbsent(callee, k -> new HashSet<>()).add(caller);
+ return super.visit(node);
+ }
+ });
+ }
+
+ return callGraph;
+ }
+
+ private static void traceCallChainInOrder(String methodName, Map> callGraph, Set visited, List callChain, Map methodMap) {
+ if (!visited.add(methodName)) {
+ return;
+ }
+ callChain.add(methodName);
+ Set callees = callGraph.get(methodName);
+ if (callees != null) {
+ for (String callee : callees) {
+ traceCallChainInOrder(callee, callGraph, visited, callChain, methodMap);
+ }
+ }
+ }
+
+ private static void drawCallGraph(String methodName, Map> callGraph, int level, Set visited) {
+ if (!visited.add(methodName)) {
+ return;
+ }
+ printIndented(methodName, level);
+ Set callers = callGraph.get(methodName);
+ if (callers != null) {
+ for (String caller : callers) {
+ drawCallGraph(caller, callGraph, level + 1, visited);
+ }
+ }
+ }
+
+ private static void printIndented(String methodName, int level) {
+ for (int i = 0; i < level; i++) {
+ System.out.print(" ");
+ }
+ System.out.println(methodName);
+ }
+
+ private static void traceCallChainsToMethod(String targetMethod, Map> callGraph, Set visited, List callChain, Map methodMap) {
+ if (!visited.add(targetMethod)) {
+ return;
+ }
+ callChain.add(targetMethod);
+ Set callees = callGraph.get(targetMethod);
+ if (callees != null) {
+ for (String callee : callees) {
+ List newCallChain = new ArrayList<>(callChain);
+ traceCallChainsToMethod(callee, callGraph, visited, newCallChain, methodMap);
+ }
+ } else {
+ if (dbMethods.contains(targetMethod)) {
+ System.out.println("Database method call chain: " + String.join(" -> ", callChain));
+ }
+ }
+ }
+
+ public static class MethodCollector extends ASTVisitor {
+ private final Map methodMap;
+
+ public MethodCollector(Map methodMap) {
+ this.methodMap = methodMap;
+ }
+
+ @Override
+ public boolean visit(MethodDeclaration node) {
+ methodMap.put(node.getName().toString(), node);
+ return super.visit(node);
+ }
+ }
+}