From d6b1571f18e78c7f997cd4f3baf4b72e5ccb8d68 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Fri, 19 Jun 2026 17:45:57 +0200 Subject: [PATCH] test1 --- .../analysis/resolver/ConstantResolver.java | 24 ++++++++ .../service/GenericEventDetector.java | 61 +++++++++++++------ .../ast/common/CodebaseContext.java | 32 ++++++++++ .../service/GenericEventDetectorTest.java | 38 ++++++++++++ 4 files changed, 136 insertions(+), 19 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java index 8e38d27..04dba49 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/ConstantResolver.java @@ -54,6 +54,30 @@ public class ConstantResolver { return resolveManual(sn, context, visited); } + if (expr instanceof MethodInvocation mi) { + IMethodBinding mb = mi.resolveMethodBinding(); + if (mb != null) { + ITypeBinding returnType = mb.getReturnType(); + if (returnType != null) { + java.util.List values = context.getEnumValues(returnType.getQualifiedName()); + if (values != null && !values.isEmpty()) { + return "ENUM_SET:" + String.join(",", values); + } + } + } else { + TypeDeclaration td = findEnclosingType(mi); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, mi.getName().getIdentifier(), true); + if (md != null && md.getReturnType2() != null) { + java.util.List values = context.getEnumValues(md.getReturnType2().toString()); + if (values != null && !values.isEmpty()) { + return "ENUM_SET:" + String.join(",", values); + } + } + } + } + } + return null; } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java index ba33d05..4f3f48b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetector.java @@ -47,10 +47,12 @@ public class GenericEventDetector { } private void processSendEvent(MethodInvocation node, CompilationUnit cu, List triggers) { - TriggerPoint trigger = buildTriggerPoint(node, cu, null); - if (trigger != null) { - log.debug("Successfully built trigger point: {}", trigger.getEvent()); - triggers.add(trigger); + List builtTriggers = buildTriggerPoints(node, cu, null); + if (builtTriggers != null) { + for (TriggerPoint trigger : builtTriggers) { + log.debug("Successfully built trigger point: {}", trigger.getEvent()); + triggers.add(trigger); + } } } @@ -62,10 +64,12 @@ public class GenericEventDetector { for (LibraryHint hint : hints) { if (calledMethod.equals(hint.getMethodFqn()) || isHintMatch(calledMethod, hint.getMethodFqn())) { - TriggerPoint trigger = buildTriggerPoint(node, cu, hint.getEvent()); - if (trigger != null) { - log.debug("Successfully built synthetic trigger point from hint: {}", trigger.getEvent()); - triggers.add(trigger); + List builtTriggers = buildTriggerPoints(node, cu, hint.getEvent()); + if (builtTriggers != null) { + for (TriggerPoint trigger : builtTriggers) { + log.debug("Successfully built synthetic trigger point from hint: {}", trigger.getEvent()); + triggers.add(trigger); + } } } } @@ -102,12 +106,12 @@ public class GenericEventDetector { return receiver.toString() + "." + methodName; } - private TriggerPoint buildTriggerPoint(MethodInvocation node, CompilationUnit cu, String forcedEvent) { + private List buildTriggerPoints(MethodInvocation node, CompilationUnit cu, String forcedEvent) { String eventValue = null; if (forcedEvent != null) { eventValue = context.resolveString(forcedEvent); } else { - if (node.arguments().isEmpty()) return null; + if (node.arguments().isEmpty()) return Collections.emptyList(); Expression eventExpr = (Expression) node.arguments().get(0); // Try MessageBuilder extraction first @@ -123,18 +127,37 @@ public class GenericEventDetector { MethodDeclaration method = findEnclosingMethod(node); TypeDeclaration type = findEnclosingType(node); - if (type == null) return null; + if (type == null) return Collections.emptyList(); String sourceState = extractSourceState(node); + + List results = new ArrayList<>(); + if (eventValue != null && eventValue.startsWith("ENUM_SET:")) { + String[] parts = eventValue.substring(9).split(","); + for (String part : parts) { + if (!part.trim().isEmpty()) { + results.add(TriggerPoint.builder() + .event(part.trim()) + .sourceState(sourceState) + .className(context.getFqn(type)) + .methodName(method != null ? method.getName().getIdentifier() : "initializer") + .sourceFile(context.getRelativePath(context.getFqn(type))) + .lineNumber(cu.getLineNumber(node.getStartPosition())) + .build()); + } + } + } else { + results.add(TriggerPoint.builder() + .event(eventValue) + .sourceState(sourceState) + .className(context.getFqn(type)) + .methodName(method != null ? method.getName().getIdentifier() : "initializer") + .sourceFile(context.getRelativePath(context.getFqn(type))) + .lineNumber(cu.getLineNumber(node.getStartPosition())) + .build()); + } - return TriggerPoint.builder() - .event(eventValue) - .sourceState(sourceState) - .className(context.getFqn(type)) - .methodName(method != null ? method.getName().getIdentifier() : "initializer") - .sourceFile(context.getRelativePath(context.getFqn(type))) - .lineNumber(cu.getLineNumber(node.getStartPosition())) - .build(); + return results; } private String extractSourceState(ASTNode node) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java index e9e53f6..20391dd 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java @@ -170,6 +170,7 @@ public class CodebaseContext { private final List allCus = new ArrayList<>(); private final Map> interfaceToImpls = new HashMap<>(); + private final Map> enumValues = new HashMap<>(); public void scan(Set rootDirs, Set customIgnorePatterns) throws IOException { this.allProperties = propertyResolver.resolveAllProperties(rootDirs); @@ -188,6 +189,8 @@ public class CodebaseContext { for (Object type : cu.types()) { if (type instanceof TypeDeclaration td) { indexType(td, packageName, javaFile); + } else if (type instanceof EnumDeclaration ed) { + indexEnum(ed, packageName, javaFile); } } } @@ -248,6 +251,35 @@ public class CodebaseContext { return Collections.emptyList(); } + private void indexEnum(EnumDeclaration ed, String parentFqn, Path javaFile) { + String simpleName = ed.getName().getIdentifier(); + String fqn = parentFqn.isEmpty() ? simpleName : parentFqn + "." + simpleName; + + classes.put(fqn, (CompilationUnit) ed.getRoot()); + classPaths.put(fqn, javaFile); + + List values = new ArrayList<>(); + for (Object o : ed.enumConstants()) { + if (o instanceof EnumConstantDeclaration ecd) { + values.add(fqn + "." + ecd.getName().getIdentifier()); + } + } + enumValues.put(fqn, values); + if (!simpleNameToFqn.containsKey(simpleName)) { + simpleNameToFqn.put(simpleName, fqn); + } + } + + public List getEnumValues(String fqnOrSimpleName) { + if (fqnOrSimpleName == null) return null; + List values = enumValues.get(fqnOrSimpleName); + if (values != null) return values; + String fqn = simpleNameToFqn.get(fqnOrSimpleName); + if (fqn != null) return enumValues.get(fqn); + // Also try stripping array or generic types if any (e.g. MyEnum[]) + return null; + } + public State resolveState(Expression expr, CompilationUnit cu) { if (expr == null) return null; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorTest.java index d345aa5..b79ba7b 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/GenericEventDetectorTest.java @@ -44,4 +44,42 @@ public class GenericEventDetectorTest { assertThat(triggers.get(0).getEvent()).isEqualTo("myEvent"); assertThat(triggers.get(0).getMethodName()).isEqualTo("a"); } + + @Test + void testEnumGetterUnionExtraction(@TempDir Path tempDir) throws IOException { + Path dir = tempDir.resolve("com/example"); + Files.createDirectories(dir); + Files.writeString(dir.resolve("MyEnum.java"), + "package com.example;\n" + + "public enum MyEnum {\n" + + " STATE_A, STATE_B;\n" + + "}\n"); + + Files.writeString(dir.resolve("MyService.java"), + "package com.example;\n" + + "import org.springframework.statemachine.StateMachine;\n" + + "public class MyService {\n" + + " private StateMachine stateMachine;\n" + + " public MyEnum getEvent() { return MyEnum.STATE_A; }\n" + // Pretend it returns the enum + " public void trigger() {\n" + + " stateMachine.sendEvent(this.getEvent());\n" + + " }\n" + + "}\n"); + + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + + GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), List.of()); + + CompilationUnit serviceCu = context.getCompilationUnits().stream() + .filter(cu -> cu.getJavaElement() == null || cu.getJavaElement().getElementName().contains("MyService")) + .findFirst().orElseThrow(); + + List triggers = detector.detect(serviceCu); + + System.out.println("TRIGGERS: " + triggers); + assertThat(triggers).hasSize(2); + assertThat(triggers).anyMatch(t -> t.getEvent().equals("com.example.MyEnum.STATE_A")); + assertThat(triggers).anyMatch(t -> t.getEvent().equals("com.example.MyEnum.STATE_B")); + } }