From c390f02389aa9ce7937cea0815bd03385b2ea44d Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 5 Jul 2026 22:00:26 +0200 Subject: [PATCH] heuristic code --- .../analysis/service/CallGraphPathFinder.java | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java index bb54082..b59a98e 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphPathFinder.java @@ -9,6 +9,7 @@ import java.util.*; @Slf4j public class CallGraphPathFinder { private final CodebaseContext context; + private final Map heuristicCache = new HashMap<>(); public CallGraphPathFinder(CodebaseContext context) { this.context = context; @@ -49,15 +50,25 @@ public class CallGraphPathFinder { } public List> findAllPaths(String start, String target, Map> graph, Set visited) { + return findAllPaths(start, target, graph, visited, new HashSet<>(), new boolean[]{false}); + } + + public List> findAllPaths(String start, String target, Map> graph, Set visited, Set unreachable, boolean[] parentHitCycle) { List> result = new ArrayList<>(); + if (unreachable.contains(start)) { + return result; + } if (start.equals(target)) { result.add(new ArrayList<>(List.of(start))); return result; } if (!visited.add(start)) { + parentHitCycle[0] = true; return result; } + boolean[] localHitCycle = new boolean[]{false}; + List neighbors = graph.get(start); if (neighbors != null) { for (CallEdge edge : neighbors) { @@ -67,7 +78,7 @@ public class CallGraphPathFinder { List path = new ArrayList<>(List.of(start, target)); result.add(path); } else { - List> subPaths = findAllPaths(neighbor, target, graph, visited); + List> subPaths = findAllPaths(neighbor, target, graph, visited, unreachable, localHitCycle); for (List subPath : subPaths) { List path = new ArrayList<>(); path.add(start); @@ -91,7 +102,7 @@ public class CallGraphPathFinder { if (superclass != null) { String superMethod = superclass + "." + methodName; if (graph.containsKey(superMethod)) { - List> superPaths = findAllPaths(superMethod, target, graph, visited); + List> superPaths = findAllPaths(superMethod, target, graph, visited, unreachable, localHitCycle); for (List superPath : superPaths) { List path = new ArrayList<>(); path.add(start); @@ -115,7 +126,7 @@ public class CallGraphPathFinder { for (String impl : impls) { String implMethod = impl + "." + methodName; if (graph.containsKey(implMethod)) { - List> implPaths = findAllPaths(implMethod, target, graph, visited); + List> implPaths = findAllPaths(implMethod, target, graph, visited, unreachable, localHitCycle); for (List implPath : implPaths) { List path = new ArrayList<>(); path.add(start); @@ -127,6 +138,13 @@ public class CallGraphPathFinder { } } visited.remove(start); + + if (localHitCycle[0]) { + parentHitCycle[0] = true; + } else if (result.isEmpty()) { + unreachable.add(start); + } + return result; } @@ -151,6 +169,18 @@ public class CallGraphPathFinder { if (neighbor == null || target == null) return false; if (neighbor.equals(target)) return true; + String cacheKey = neighbor + "|" + target; + Boolean cached = heuristicCache.get(cacheKey); + if (cached != null) return cached; + + boolean result = calculateHeuristicMatch(neighbor, target); + heuristicCache.put(cacheKey, result); + return result; + } + + private boolean calculateHeuristicMatch(String neighbor, String target) { + if (neighbor.equals(target)) return true; + if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) { int lastDotNeighbor = neighbor.lastIndexOf('.'); int lastDotTarget = target.lastIndexOf('.');