new sketch
This commit is contained in:
109
src/TODO.md
109
src/TODO.md
@@ -1,4 +1,5 @@
|
|||||||
1. extract state machines from this as well
|
1. extract state machines from this as well
|
||||||
|
2. extract from all possible formats (ask chatgpt)
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
||||||
@@ -49,4 +50,112 @@ public class DynamicStateMachineExample {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
1. define how java function can be implemented
|
||||||
|
1.2. for this analyse bytecode manipulation such as project lombok
|
||||||
|
2. get a diagram
|
||||||
|
3. get a diagram with slicing, for example, slice by setValue on an ENUM field -> identify saga
|
||||||
|
|
||||||
|
|
||||||
|
1. make a list of all messages on message queues, how they're remapped and then who receives them
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
```java
|
||||||
|
|
||||||
|
import com.github.javaparser.StaticJavaParser;
|
||||||
|
import com.github.javaparser.ast.CompilationUnit;
|
||||||
|
import com.github.javaparser.ast.body.MethodDeclaration;
|
||||||
|
import com.github.javaparser.ast.expr.MethodCallExpr;
|
||||||
|
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class MethodCallGraphGenerator {
|
||||||
|
|
||||||
|
static class MethodNode {
|
||||||
|
String className;
|
||||||
|
String methodName;
|
||||||
|
|
||||||
|
public MethodNode(String className, String methodName) {
|
||||||
|
this.className = className;
|
||||||
|
this.methodName = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return className + "." + methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(className, methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof MethodNode other) {
|
||||||
|
return this.className.equals(other.className) && this.methodName.equals(other.methodName);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<MethodNode, Set<MethodNode>> callGraph = new HashMap<>();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
File sourceDir = new File("src"); // Change to your Java source directory
|
||||||
|
parseDirectory(sourceDir);
|
||||||
|
|
||||||
|
generateDotFile("callgraph.dot");
|
||||||
|
System.out.println("Call graph generated to callgraph.dot");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseDirectory(File dir) throws Exception {
|
||||||
|
for (File file : Objects.requireNonNull(dir.listFiles())) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
parseDirectory(file);
|
||||||
|
} else if (file.getName().endsWith(".java")) {
|
||||||
|
parseFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseFile(File file) throws Exception {
|
||||||
|
CompilationUnit cu = StaticJavaParser.parse(new FileInputStream(file));
|
||||||
|
cu.findAll(MethodDeclaration.class).forEach(method -> {
|
||||||
|
String className = cu.getPrimaryTypeName().orElse("Unknown");
|
||||||
|
MethodNode caller = new MethodNode(className, method.getNameAsString());
|
||||||
|
|
||||||
|
callGraph.putIfAbsent(caller, new HashSet<>());
|
||||||
|
|
||||||
|
method.accept(new VoidVisitorAdapter<Void>() {
|
||||||
|
@Override
|
||||||
|
public void visit(MethodCallExpr call, Void arg) {
|
||||||
|
super.visit(call, arg);
|
||||||
|
String calleeName = call.getNameAsString();
|
||||||
|
MethodNode callee = new MethodNode("Unknown", calleeName); // Could be refined with better context
|
||||||
|
callGraph.get(caller).add(callee);
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void generateDotFile(String filename) throws Exception {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("digraph G {\n");
|
||||||
|
for (var caller : callGraph.keySet()) {
|
||||||
|
for (var callee : callGraph.get(caller)) {
|
||||||
|
sb.append(" \"").append(caller).append("\" -> \"").append(callee).append("\";\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("}");
|
||||||
|
java.nio.file.Files.writeString(new File(filename).toPath(), sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package click.kamil.callgraphexporter;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.core.JavaCore;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class JdtResolvedCallGraph {
|
||||||
|
|
||||||
|
// Use signature as the unique key
|
||||||
|
static Map<MethodSignature, Set<MethodSignature>> callGraph = new HashMap<>();
|
||||||
|
|
||||||
|
// Optionally store metadata
|
||||||
|
static Map<MethodSignature, MethodMetadata> metadataMap = new HashMap<>();
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
File sourceDir = new File("src/main/java/click/kamil/callgraphexporter/simple");
|
||||||
|
|
||||||
|
List<File> javaFiles = new ArrayList<>();
|
||||||
|
collectJavaFiles(sourceDir, javaFiles);
|
||||||
|
|
||||||
|
for (File file : javaFiles) {
|
||||||
|
parseFileWithBinding(file, sourceDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateDotFile("callgraph.dot");
|
||||||
|
System.out.println("Call graph saved to callgraph.dot");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collectJavaFiles(File dir, List<File> javaFiles) {
|
||||||
|
for (File file : Objects.requireNonNull(dir.listFiles())) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
collectJavaFiles(file, javaFiles);
|
||||||
|
} else if (file.getName().endsWith(".java")) {
|
||||||
|
javaFiles.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseFileWithBinding(File file, File sourceRoot) throws IOException {
|
||||||
|
String source = Files.readString(file.toPath());
|
||||||
|
|
||||||
|
ASTParser parser = ASTParser.newParser(AST.JLS21);
|
||||||
|
parser.setSource(source.toCharArray());
|
||||||
|
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||||
|
parser.setResolveBindings(true);
|
||||||
|
parser.setBindingsRecovery(true);
|
||||||
|
|
||||||
|
parser.setEnvironment(
|
||||||
|
null,
|
||||||
|
new String[]{sourceRoot.getAbsolutePath()},
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
parser.setUnitName(file.getName());
|
||||||
|
|
||||||
|
Map<String, String> options = JavaCore.getOptions();
|
||||||
|
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_17);
|
||||||
|
parser.setCompilerOptions(options);
|
||||||
|
|
||||||
|
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||||
|
|
||||||
|
cu.accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(MethodDeclaration method) {
|
||||||
|
IMethodBinding binding = method.resolveBinding();
|
||||||
|
if (binding == null) return super.visit(method);
|
||||||
|
|
||||||
|
MethodSignature callerSig = toSignature(binding);
|
||||||
|
MethodMetadata callerMeta = toMetadata(binding, method);
|
||||||
|
metadataMap.put(callerSig, callerMeta);
|
||||||
|
callGraph.putIfAbsent(callerSig, new HashSet<>());
|
||||||
|
|
||||||
|
method.accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(MethodInvocation node) {
|
||||||
|
IMethodBinding calleeBinding = node.resolveMethodBinding();
|
||||||
|
if (calleeBinding != null) {
|
||||||
|
MethodSignature calleeSig = toSignature(calleeBinding);
|
||||||
|
callGraph.get(callerSig).add(calleeSig);
|
||||||
|
// You can collect metadata for callees too if needed
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return super.visit(method);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodSignature toSignature(IMethodBinding binding) {
|
||||||
|
String packageName = binding.getDeclaringClass().getPackage().getName();
|
||||||
|
String className = binding.getDeclaringClass().getName();
|
||||||
|
String methodName = binding.getName();
|
||||||
|
|
||||||
|
List<String> paramTypes = new ArrayList<>();
|
||||||
|
for (ITypeBinding param : binding.getParameterTypes()) {
|
||||||
|
paramTypes.add(param.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MethodSignature(packageName, className, methodName, paramTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodMetadata toMetadata(IMethodBinding binding, MethodDeclaration method) {
|
||||||
|
String returnType = binding.getReturnType().getName();
|
||||||
|
|
||||||
|
Set<String> exceptions = new HashSet<>();
|
||||||
|
for (ITypeBinding ex : binding.getExceptionTypes()) {
|
||||||
|
exceptions.add(ex.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Modifiers> modifiers = extractModifiers(binding, method);
|
||||||
|
List<String> annotations = new ArrayList<>();
|
||||||
|
|
||||||
|
return new MethodMetadata(returnType, exceptions, modifiers, annotations, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void generateDotFile(String filename) throws IOException {
|
||||||
|
StringBuilder sb = new StringBuilder("digraph G {\n");
|
||||||
|
for (MethodSignature caller : callGraph.keySet()) {
|
||||||
|
for (MethodSignature callee : callGraph.get(caller)) {
|
||||||
|
sb.append(" \"").append(caller).append("\" -> \"").append(callee).append("\";\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("}");
|
||||||
|
Files.writeString(new File(filename).toPath(), sb.toString());
|
||||||
|
}
|
||||||
|
private static Set<Modifiers> extractModifiers(IMethodBinding binding, MethodDeclaration method) {
|
||||||
|
Set<Modifiers> result = new HashSet<>();
|
||||||
|
int mods = binding.getModifiers();
|
||||||
|
|
||||||
|
// 1. From bitmask
|
||||||
|
if (Modifier.isPublic(mods)) result.add(Modifiers.PUBLIC);
|
||||||
|
if (Modifier.isPrivate(mods)) result.add(Modifiers.PRIVATE);
|
||||||
|
if (Modifier.isProtected(mods)) result.add(Modifiers.PROTECTED);
|
||||||
|
if (Modifier.isStatic(mods)) result.add(Modifiers.STATIC);
|
||||||
|
if (Modifier.isAbstract(mods)) result.add(Modifiers.ABSTRACT);
|
||||||
|
if (Modifier.isFinal(mods)) result.add(Modifiers.FINAL);
|
||||||
|
if (Modifier.isNative(mods)) result.add(Modifiers.NATIVE);
|
||||||
|
if (Modifier.isSynchronized(mods)) result.add(Modifiers.SYNCHRONIZED);
|
||||||
|
if (Modifier.isStrictfp(mods)) result.add(Modifiers.STRICTFP);
|
||||||
|
|
||||||
|
// 2. From modifiers list for special cases
|
||||||
|
for (Object mod : method.modifiers()) {
|
||||||
|
if (mod instanceof Modifier keyword) {
|
||||||
|
if (keyword.isDefault()) result.add(Modifiers.DEFAULT);
|
||||||
|
// NOTE: isSealed and isNonSealed are class-level only
|
||||||
|
}
|
||||||
|
// If needed, check for annotation modifiers here
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/main/java/click/kamil/callgraphexporter/Method.java
Normal file
15
src/main/java/click/kamil/callgraphexporter/Method.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package click.kamil.callgraphexporter;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class Method {
|
||||||
|
private MethodSignature signature;
|
||||||
|
private MethodMetadata metadata;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package click.kamil.callgraphexporter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public record MethodMetadata(
|
||||||
|
String returnType,
|
||||||
|
Set<String> thrownExceptions,
|
||||||
|
Set<Modifiers> modifiers,
|
||||||
|
List<String> annotations,
|
||||||
|
String documentationComment // Optional: Javadoc
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package click.kamil.callgraphexporter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record MethodSignature(
|
||||||
|
String packageName,
|
||||||
|
String className,
|
||||||
|
String methodName,
|
||||||
|
List<String> parameterTypes
|
||||||
|
) {
|
||||||
|
}
|
||||||
19
src/main/java/click/kamil/callgraphexporter/Modifiers.java
Normal file
19
src/main/java/click/kamil/callgraphexporter/Modifiers.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package click.kamil.callgraphexporter;
|
||||||
|
|
||||||
|
public enum Modifiers {
|
||||||
|
PUBLIC,
|
||||||
|
PROTECTED,
|
||||||
|
PRIVATE,
|
||||||
|
STATIC,
|
||||||
|
ABSTRACT,
|
||||||
|
FINAL,
|
||||||
|
NATIVE,
|
||||||
|
SYNCHRONIZED,
|
||||||
|
TRANSIENT,
|
||||||
|
VOLATILE,
|
||||||
|
STRICTFP,
|
||||||
|
DEFAULT,
|
||||||
|
SEALED,
|
||||||
|
NON_SEALED,
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package click.kamil.callgraphexporter.simple;
|
||||||
|
|
||||||
|
public class Kamil {
|
||||||
|
|
||||||
|
void a(){
|
||||||
|
b();
|
||||||
|
}
|
||||||
|
void b(){
|
||||||
|
c();
|
||||||
|
}
|
||||||
|
void c(){}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package click.kamil.callgraphexporter.simple;
|
||||||
|
|
||||||
|
public class Kamil2 {
|
||||||
|
void ka() {
|
||||||
|
new Kamil().b();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ public class PlantUml implements StateMachineExporter {
|
|||||||
default -> "000000";
|
default -> "000000";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
private final String LAMBDA = "λ";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String export(List<Transition> transitions,
|
public String export(List<Transition> transitions,
|
||||||
@@ -127,7 +128,7 @@ public class PlantUml implements StateMachineExporter {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (useLambdaGuards && t.isLambdaGuard()) {
|
if (useLambdaGuards && t.isLambdaGuard()) {
|
||||||
return "lambda";
|
return LAMBDA;
|
||||||
}
|
}
|
||||||
return guard
|
return guard
|
||||||
.replaceAll("[\\n\\r]+", " ")
|
.replaceAll("[\\n\\r]+", " ")
|
||||||
@@ -147,7 +148,7 @@ public class PlantUml implements StateMachineExporter {
|
|||||||
int size = Math.min(actions.size(), isLambdaFlags.size());
|
int size = Math.min(actions.size(), isLambdaFlags.size());
|
||||||
|
|
||||||
return IntStream.range(0, size)
|
return IntStream.range(0, size)
|
||||||
.mapToObj(i -> (useLambdaGuards && isLambdaFlags.get(i)) ? "lambda" : actions.get(i))
|
.mapToObj(i -> (useLambdaGuards && isLambdaFlags.get(i)) ? LAMBDA : actions.get(i))
|
||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class Scxml implements StateMachineExporter {
|
public class Scxml implements StateMachineExporter {
|
||||||
|
private static final String LAMBDA = "λ";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String export(List<Transition> transitions,
|
public String export(List<Transition> transitions,
|
||||||
@@ -74,7 +75,9 @@ public class Scxml implements StateMachineExporter {
|
|||||||
|
|
||||||
private static String getGuardText(Transition t, boolean useLambdaGuards) {
|
private static String getGuardText(Transition t, boolean useLambdaGuards) {
|
||||||
if (t.getGuard() == null || t.getGuard().isBlank()) return "";
|
if (t.getGuard() == null || t.getGuard().isBlank()) return "";
|
||||||
if (useLambdaGuards && t.isLambdaGuard()) return "lambda";
|
if (useLambdaGuards && t.isLambdaGuard()) {
|
||||||
|
return LAMBDA;
|
||||||
|
}
|
||||||
return t.getGuard().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim();
|
return t.getGuard().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user