Compare commits
3 Commits
180a283383
...
7807df57ca
| Author | SHA1 | Date | |
|---|---|---|---|
| 7807df57ca | |||
| 82719489aa | |||
| c390f02389 |
BIN
TestHeuristic.class
Normal file
BIN
TestHeuristic.class
Normal file
Binary file not shown.
17
TestHeuristic.java
Normal file
17
TestHeuristic.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class TestHeuristic {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(isFullyQualifiedMethod("TransformerImpl.transform"));
|
||||||
|
System.out.println(isFullyQualifiedMethod("Transformer.transform"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isFullyQualifiedMethod(String methodFqn) {
|
||||||
|
if (methodFqn == null || !methodFqn.contains(".")) return false;
|
||||||
|
int lastDot = methodFqn.lastIndexOf('.');
|
||||||
|
String classPart = methodFqn.substring(0, lastDot);
|
||||||
|
if (classPart.contains(".")) return true;
|
||||||
|
if (!classPart.isEmpty() && Character.isUpperCase(classPart.charAt(0))) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -194,20 +194,38 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
|
||||||
|
private final java.util.Map<String, Boolean> guardedCache = new java.util.HashMap<>();
|
||||||
|
private static final java.util.regex.Pattern SUFFIX_PATTERN = java.util.regex.Pattern.compile("(?i)(.+)(Event|Action|Transition|Command)(s)?$");
|
||||||
|
private static final java.util.regex.Pattern FQN_PATTERN = java.util.regex.Pattern.compile("^.*\\.([A-Z0-9_]+)$");
|
||||||
|
|
||||||
private String simplify(String name) {
|
private String simplify(String name) {
|
||||||
if (name == null) return null;
|
if (name == null) return null;
|
||||||
|
if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
|
||||||
|
|
||||||
// Strip common suffixes
|
// Strip common suffixes
|
||||||
String simplified = name.replaceAll("(?i)(.+)(Event|Action|Transition|Command)(s)?$", "$1");
|
String simplified = SUFFIX_PATTERN.matcher(name).replaceAll("$1");
|
||||||
if (simplified.isEmpty()) {
|
if (simplified.isEmpty()) {
|
||||||
simplified = name;
|
simplified = name;
|
||||||
}
|
}
|
||||||
// Simplify full identifiers to just the last part (enum name)
|
// Simplify full identifiers to just the last part (enum name)
|
||||||
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
|
simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
|
||||||
// For remaining full caps with underscores (like EVENT_X), keep as is or try to simplify
|
|
||||||
|
simplifyCache.put(name, simplified);
|
||||||
return simplified;
|
return simplified;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
||||||
|
if (smEvent == null || triggerEvent == null) return false;
|
||||||
|
String cacheKey = smEvent + "|" + triggerEvent;
|
||||||
|
if (guardedCache.containsKey(cacheKey)) return guardedCache.get(cacheKey);
|
||||||
|
|
||||||
|
boolean result = calculateGuardedContains(smEvent, triggerEvent);
|
||||||
|
guardedCache.put(cacheKey, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean calculateGuardedContains(String smEvent, String triggerEvent) {
|
||||||
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
||||||
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||||
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||||
@@ -225,7 +243,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
// Guard: If the matching part is very short (< 4 chars), it must be bounded by word separators
|
// Guard: If the matching part is very short (< 4 chars), it must be bounded by word separators
|
||||||
// to avoid matching completely unrelated words (e.g., "PAY" matching "PAYMENT" or "E" matching "UPDATE")
|
// to avoid matching completely unrelated words (e.g., "PAY" matching "PAYMENT" or "E" matching "UPDATE")
|
||||||
if (shorter.length() < 4) {
|
if (shorter.length() < 4) {
|
||||||
return longer.matches(".*(^|_|-)" + shorter + "(_|-|$).*");
|
return longer.matches(".*(^|_|-)" + java.util.regex.Pattern.quote(shorter) + "(_|-|$).*");
|
||||||
}
|
}
|
||||||
// For longer strings, a straight contains is usually safe enough
|
// For longer strings, a straight contains is usually safe enough
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -87,8 +87,22 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
return isGuardedContains(smEvent, triggerEvent);
|
return isGuardedContains(smEvent, triggerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
|
||||||
|
private final java.util.Map<String, Boolean> guardedCache = new java.util.HashMap<>();
|
||||||
|
private static final java.util.regex.Pattern SUFFIX_PATTERN = java.util.regex.Pattern.compile("(?i)(.+)(Event|Action|Transition|Command)(s)?$");
|
||||||
|
private static final java.util.regex.Pattern FQN_PATTERN = java.util.regex.Pattern.compile("^.*\\.([A-Z0-9_]+)$");
|
||||||
|
|
||||||
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
||||||
if (smEvent == null || triggerEvent == null) return false;
|
if (smEvent == null || triggerEvent == null) return false;
|
||||||
|
String cacheKey = smEvent + "|" + triggerEvent;
|
||||||
|
if (guardedCache.containsKey(cacheKey)) return guardedCache.get(cacheKey);
|
||||||
|
|
||||||
|
boolean result = calculateGuardedContains(smEvent, triggerEvent);
|
||||||
|
guardedCache.put(cacheKey, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean calculateGuardedContains(String smEvent, String triggerEvent) {
|
||||||
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
||||||
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||||
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||||
@@ -137,11 +151,15 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
|
|
||||||
private String simplify(String name) {
|
private String simplify(String name) {
|
||||||
if (name == null) return null;
|
if (name == null) return null;
|
||||||
String simplified = name.replaceAll("(?i)(.+)(Event|Action|Transition|Command)(s)?$", "$1");
|
if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
|
||||||
|
|
||||||
|
String simplified = SUFFIX_PATTERN.matcher(name).replaceAll("$1");
|
||||||
if (simplified.isEmpty()) {
|
if (simplified.isEmpty()) {
|
||||||
simplified = name;
|
simplified = name;
|
||||||
}
|
}
|
||||||
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
|
simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
|
||||||
|
|
||||||
|
simplifyCache.put(name, simplified);
|
||||||
return simplified;
|
return simplified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import java.util.*;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class CallGraphPathFinder {
|
public class CallGraphPathFinder {
|
||||||
private final CodebaseContext context;
|
private final CodebaseContext context;
|
||||||
|
private final Map<String, Boolean> heuristicCache = new HashMap<>();
|
||||||
|
|
||||||
public CallGraphPathFinder(CodebaseContext context) {
|
public CallGraphPathFinder(CodebaseContext context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -49,15 +50,25 @@ public class CallGraphPathFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
||||||
|
return findAllPaths(start, target, graph, visited, new HashSet<>(), new boolean[]{false});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited, Set<String> unreachable, boolean[] parentHitCycle) {
|
||||||
List<List<String>> result = new ArrayList<>();
|
List<List<String>> result = new ArrayList<>();
|
||||||
|
if (unreachable.contains(start)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
if (start.equals(target)) {
|
if (start.equals(target)) {
|
||||||
result.add(new ArrayList<>(List.of(start)));
|
result.add(new ArrayList<>(List.of(start)));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (!visited.add(start)) {
|
if (!visited.add(start)) {
|
||||||
|
parentHitCycle[0] = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean[] localHitCycle = new boolean[]{false};
|
||||||
|
|
||||||
List<CallEdge> neighbors = graph.get(start);
|
List<CallEdge> neighbors = graph.get(start);
|
||||||
if (neighbors != null) {
|
if (neighbors != null) {
|
||||||
for (CallEdge edge : neighbors) {
|
for (CallEdge edge : neighbors) {
|
||||||
@@ -67,7 +78,7 @@ public class CallGraphPathFinder {
|
|||||||
List<String> path = new ArrayList<>(List.of(start, target));
|
List<String> path = new ArrayList<>(List.of(start, target));
|
||||||
result.add(path);
|
result.add(path);
|
||||||
} else {
|
} else {
|
||||||
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited);
|
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited, unreachable, localHitCycle);
|
||||||
for (List<String> subPath : subPaths) {
|
for (List<String> subPath : subPaths) {
|
||||||
List<String> path = new ArrayList<>();
|
List<String> path = new ArrayList<>();
|
||||||
path.add(start);
|
path.add(start);
|
||||||
@@ -91,7 +102,7 @@ public class CallGraphPathFinder {
|
|||||||
if (superclass != null) {
|
if (superclass != null) {
|
||||||
String superMethod = superclass + "." + methodName;
|
String superMethod = superclass + "." + methodName;
|
||||||
if (graph.containsKey(superMethod)) {
|
if (graph.containsKey(superMethod)) {
|
||||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited);
|
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited, unreachable, localHitCycle);
|
||||||
for (List<String> superPath : superPaths) {
|
for (List<String> superPath : superPaths) {
|
||||||
List<String> path = new ArrayList<>();
|
List<String> path = new ArrayList<>();
|
||||||
path.add(start);
|
path.add(start);
|
||||||
@@ -115,7 +126,7 @@ public class CallGraphPathFinder {
|
|||||||
for (String impl : impls) {
|
for (String impl : impls) {
|
||||||
String implMethod = impl + "." + methodName;
|
String implMethod = impl + "." + methodName;
|
||||||
if (graph.containsKey(implMethod)) {
|
if (graph.containsKey(implMethod)) {
|
||||||
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited);
|
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited, unreachable, localHitCycle);
|
||||||
for (List<String> implPath : implPaths) {
|
for (List<String> implPath : implPaths) {
|
||||||
List<String> path = new ArrayList<>();
|
List<String> path = new ArrayList<>();
|
||||||
path.add(start);
|
path.add(start);
|
||||||
@@ -127,6 +138,13 @@ public class CallGraphPathFinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
visited.remove(start);
|
visited.remove(start);
|
||||||
|
|
||||||
|
if (localHitCycle[0]) {
|
||||||
|
parentHitCycle[0] = true;
|
||||||
|
} else if (result.isEmpty()) {
|
||||||
|
unreachable.add(start);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +169,19 @@ public class CallGraphPathFinder {
|
|||||||
if (neighbor == null || target == null) return false;
|
if (neighbor == null || target == null) return false;
|
||||||
if (neighbor.equals(target)) return true;
|
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) {
|
||||||
|
System.out.println("calculateHeuristicMatch: neighbor=" + neighbor + ", target=" + target);
|
||||||
|
if (neighbor.equals(target)) return true;
|
||||||
|
|
||||||
if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) {
|
if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) {
|
||||||
int lastDotNeighbor = neighbor.lastIndexOf('.');
|
int lastDotNeighbor = neighbor.lastIndexOf('.');
|
||||||
int lastDotTarget = target.lastIndexOf('.');
|
int lastDotTarget = target.lastIndexOf('.');
|
||||||
@@ -172,7 +203,6 @@ public class CallGraphPathFinder {
|
|||||||
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||||
|
|
||||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||||
if (simpleClassNeighbor.contains(simpleClassTarget) || simpleClassTarget.contains(simpleClassNeighbor)) return true;
|
|
||||||
|
|
||||||
List<String> impls = context.getImplementations(classTarget);
|
List<String> impls = context.getImplementations(classTarget);
|
||||||
if (impls != null) {
|
if (impls != null) {
|
||||||
@@ -192,7 +222,21 @@ public class CallGraphPathFinder {
|
|||||||
|
|
||||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||||
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
||||||
return simpleNeighbor.equals(simpleTarget);
|
if (!simpleNeighbor.equals(simpleTarget)) return false;
|
||||||
|
|
||||||
|
String classNeighbor = neighbor.contains(".") ? neighbor.substring(0, neighbor.lastIndexOf('.')) : null;
|
||||||
|
String classTarget = target.contains(".") ? target.substring(0, target.lastIndexOf('.')) : null;
|
||||||
|
String simpleClassTarget = classTarget != null && classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||||
|
String simpleClassNeighbor = classNeighbor != null && classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||||
|
|
||||||
|
if (simpleClassNeighbor != null && simpleClassTarget != null) {
|
||||||
|
if (simpleClassNeighbor.equalsIgnoreCase(simpleClassTarget)) return true;
|
||||||
|
// e.g. "this" vs "com.example.Machine"
|
||||||
|
if (simpleClassNeighbor.equals("this") || simpleClassNeighbor.equals("super")) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFullyQualifiedMethod(String methodFqn) {
|
private boolean isFullyQualifiedMethod(String methodFqn) {
|
||||||
|
|||||||
@@ -525,14 +525,39 @@ public class VariableTracer {
|
|||||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||||
|
|
||||||
List<String> impls = context.getImplementations(classTarget);
|
List<String> impls = context.getImplementations(classTarget);
|
||||||
if (impls != null && impls.contains(classNeighbor)) return true;
|
if (impls != null) {
|
||||||
|
for (String impl : impls) {
|
||||||
|
if (impl.equals(classNeighbor) || impl.endsWith("." + classNeighbor)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<String> implsN = context.getImplementations(classNeighbor);
|
List<String> implsN = context.getImplementations(classNeighbor);
|
||||||
if (implsN != null && implsN.contains(classTarget)) return true;
|
if (implsN != null) {
|
||||||
|
for (String impl : implsN) {
|
||||||
|
if (impl.equals(classTarget) || impl.endsWith("." + classTarget)) return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||||
|
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
||||||
|
if (!simpleNeighbor.equals(simpleTarget)) return false;
|
||||||
|
|
||||||
|
String classNeighbor = neighbor.contains(".") ? neighbor.substring(0, neighbor.lastIndexOf('.')) : null;
|
||||||
|
String classTarget = target.contains(".") ? target.substring(0, target.lastIndexOf('.')) : null;
|
||||||
|
String simpleClassTarget = classTarget != null && classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||||
|
String simpleClassNeighbor = classNeighbor != null && classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||||
|
|
||||||
|
if (simpleClassNeighbor != null && simpleClassTarget != null) {
|
||||||
|
if (simpleClassNeighbor.equalsIgnoreCase(simpleClassTarget)) return true;
|
||||||
|
if (simpleClassNeighbor.equals("this") || simpleClassNeighbor.equals("super")) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||||
ASTNode parent = node.getParent();
|
ASTNode parent = node.getParent();
|
||||||
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user