|
|
|
|
@@ -2,6 +2,7 @@ package click.kamil.springstatemachineexporter.ast.app;
|
|
|
|
|
|
|
|
|
|
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
|
|
|
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
|
|
|
|
import lombok.Getter;
|
|
|
|
|
import org.eclipse.jdt.core.dom.*;
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
@@ -19,7 +20,7 @@ public class StateMachineAggregator {
|
|
|
|
|
return allTransitions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void aggregateTransitionsRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, List<Transition> allTransitions, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
private void aggregateTransitionsRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, List<Transition> allTransitions, Set<String> visitedClasses, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
|
|
|
|
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
|
|
|
|
|
|
|
|
|
@@ -28,48 +29,62 @@ public class StateMachineAggregator {
|
|
|
|
|
allTransitions.addAll(parseTransitionsWithMethodCalls(configureMethod, searchStartClass, visitedMethods, argsMap));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hierarchy - Superclass
|
|
|
|
|
Type superclassType = currentTd.getSuperclassType();
|
|
|
|
|
if (superclassType != null) {
|
|
|
|
|
String superclassName = getSimpleName(superclassType);
|
|
|
|
|
TypeDeclaration superclassTd = context.getTypeDeclaration(superclassName);
|
|
|
|
|
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
|
|
|
|
if (superclassTd != null) {
|
|
|
|
|
aggregateTransitionsRecursive(superclassTd, searchStartClass, allTransitions, visitedClasses, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseTransitionsWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
List<Transition> transitions = new ArrayList<>();
|
|
|
|
|
if (visitedMethods.contains(method)) return transitions;
|
|
|
|
|
visitedMethods.add(method);
|
|
|
|
|
|
|
|
|
|
Block body = method.getBody();
|
|
|
|
|
if (body == null) return transitions;
|
|
|
|
|
|
|
|
|
|
for (Object stmt : body.statements()) {
|
|
|
|
|
if (stmt instanceof ExpressionStatement es) {
|
|
|
|
|
transitions.addAll(parseTransitionsInExpression(es.getExpression(), searchStartClass, visitedMethods, argsMap));
|
|
|
|
|
} else if (stmt instanceof VariableDeclarationStatement vds) {
|
|
|
|
|
for (Object fragmentObj : vds.fragments()) {
|
|
|
|
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
|
|
|
|
transitions.addAll(parseTransitionsInExpression(fragment.getInitializer(), searchStartClass, visitedMethods, argsMap));
|
|
|
|
|
}
|
|
|
|
|
// Hierarchy - Interfaces
|
|
|
|
|
for (Object interfaceTypeObj : currentTd.superInterfaceTypes()) {
|
|
|
|
|
if (interfaceTypeObj instanceof Type interfaceType) {
|
|
|
|
|
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType));
|
|
|
|
|
if (interfaceTd != null) {
|
|
|
|
|
aggregateTransitionsRecursive(interfaceTd, searchStartClass, allTransitions, visitedClasses, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
} else if (stmt instanceof EnhancedForStatement efs) {
|
|
|
|
|
transitions.addAll(parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, true, null, null));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return transitions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseTransitionsInExpression(Expression expr, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
private record MethodVisit(MethodDeclaration method, Map<String, Object> args) {}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseTransitionsWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
MethodVisit visit = new MethodVisit(method, new HashMap<>(argsMap));
|
|
|
|
|
if (visitedMethods.contains(visit)) {
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
}
|
|
|
|
|
visitedMethods.add(visit);
|
|
|
|
|
|
|
|
|
|
Block body = method.getBody();
|
|
|
|
|
if (body == null) {
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parseTransitionsInBlock(body, searchStartClass, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseTransitionsInExpression(Expression expr, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
List<Transition> transitions = new ArrayList<>();
|
|
|
|
|
if (expr instanceof MethodInvocation mi) {
|
|
|
|
|
if (isTransitionChain(mi)) {
|
|
|
|
|
if (isTransitionChainEntry(mi)) {
|
|
|
|
|
transitions.addAll(AstTransitionParser.parseTransitionsFromExpression(mi, convertToExpressionMap(argsMap)));
|
|
|
|
|
} else if (isTransitionChainContinuation(mi, argsMap)) {
|
|
|
|
|
transitions.addAll(AstTransitionParser.parseTransitionsFromExpression(mi, convertToExpressionMap(argsMap), true));
|
|
|
|
|
} else if (isForEach(mi)) {
|
|
|
|
|
transitions.addAll(parseForEach(mi, searchStartClass, visitedMethods, argsMap, true, null, null));
|
|
|
|
|
} else {
|
|
|
|
|
// Check if it's a call on a lambda parameter or a variable that resolved to a lambda
|
|
|
|
|
Expression receiver = mi.getExpression();
|
|
|
|
|
if (receiver instanceof SimpleName sn) {
|
|
|
|
|
Object resolved = argsMap.get(sn.getIdentifier());
|
|
|
|
|
if (resolved instanceof LambdaExpression lambda) {
|
|
|
|
|
transitions.addAll(parseLambdaInvocation(lambda, mi.arguments(), searchStartClass, visitedMethods, argsMap, true, null, null));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
|
|
|
|
if (calledMethod != null) {
|
|
|
|
|
Map<String, Object> nextArgsMap = buildArgsMap(calledMethod, mi, argsMap);
|
|
|
|
|
@@ -84,7 +99,29 @@ public class StateMachineAggregator {
|
|
|
|
|
return mi.getName().getIdentifier().equals("forEach");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseForEach(MethodInvocation forEachMi, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
|
|
|
|
private List<Transition> parseLambdaInvocation(LambdaExpression lambda, List<?> arguments, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
|
|
|
|
Map<String, Object> lambdaArgsMap = new HashMap<>(argsMap);
|
|
|
|
|
List<?> params = lambda.parameters();
|
|
|
|
|
for (int i = 0; i < params.size() && i < arguments.size(); i++) {
|
|
|
|
|
Object p = params.get(i);
|
|
|
|
|
String paramName = "";
|
|
|
|
|
if (p instanceof VariableDeclaration vp) paramName = vp.getName().getIdentifier();
|
|
|
|
|
if (!paramName.isEmpty()) {
|
|
|
|
|
lambdaArgsMap.put(paramName, resolveExpression((Expression) arguments.get(i), argsMap));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isTransitions) {
|
|
|
|
|
if (lambda.getBody() instanceof Block block) return parseTransitionsInBlock(block, searchStartClass, visitedMethods, lambdaArgsMap);
|
|
|
|
|
else if (lambda.getBody() instanceof Expression lambdaExpr) return parseTransitionsInExpression(lambdaExpr, searchStartClass, visitedMethods, lambdaArgsMap);
|
|
|
|
|
} else {
|
|
|
|
|
if (lambda.getBody() instanceof Block block) parseStatesInBlock(block, searchStartClass, initialStates, endStates, visitedMethods, lambdaArgsMap);
|
|
|
|
|
else if (lambda.getBody() instanceof Expression lambdaExpr) parseStatesInExpression(lambdaExpr, searchStartClass, initialStates, endStates, visitedMethods, lambdaArgsMap);
|
|
|
|
|
}
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseForEach(MethodInvocation forEachMi, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
|
|
|
|
List<Transition> transitions = new ArrayList<>();
|
|
|
|
|
Expression receiver = forEachMi.getExpression();
|
|
|
|
|
List<Expression> elements = unrollCollection(receiver, argsMap);
|
|
|
|
|
@@ -109,7 +146,7 @@ public class StateMachineAggregator {
|
|
|
|
|
return transitions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseEnhancedForLoop(EnhancedForStatement efs, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
|
|
|
|
private List<Transition> parseEnhancedForLoop(EnhancedForStatement efs, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
|
|
|
|
List<Transition> transitions = new ArrayList<>();
|
|
|
|
|
List<Expression> elements = unrollCollection(efs.getExpression(), argsMap);
|
|
|
|
|
String paramName = efs.getParameter().getName().getIdentifier();
|
|
|
|
|
@@ -135,6 +172,13 @@ public class StateMachineAggregator {
|
|
|
|
|
Expression receiver = mi.getExpression();
|
|
|
|
|
if ((name.equals("of") && receiver != null && (receiver.toString().equals("Stream") || receiver.toString().equals("List")))
|
|
|
|
|
|| (name.equals("asList") && receiver != null && receiver.toString().equals("Arrays"))) {
|
|
|
|
|
if (mi.arguments().size() == 1) {
|
|
|
|
|
Expression arg = (Expression) mi.arguments().get(0);
|
|
|
|
|
if (arg instanceof SimpleName sn) {
|
|
|
|
|
Object resolved = argsMap.get(sn.getIdentifier());
|
|
|
|
|
if (resolved instanceof List list) return (List<Expression>) list;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return (List<Expression>) mi.arguments();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -155,27 +199,62 @@ public class StateMachineAggregator {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Transition> parseTransitionsInBlock(Block block, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
private List<Transition> parseTransitionsInBlock(Block block, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
List<Transition> transitions = new ArrayList<>();
|
|
|
|
|
for (Object stmt : block.statements()) {
|
|
|
|
|
if (stmt instanceof ExpressionStatement es) transitions.addAll(parseTransitionsInExpression(es.getExpression(), searchStartClass, visitedMethods, argsMap));
|
|
|
|
|
else if (stmt instanceof VariableDeclarationStatement vds) {
|
|
|
|
|
for (Object fragmentObj : vds.fragments()) {
|
|
|
|
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
|
|
|
|
Expression initializer = fragment.getInitializer();
|
|
|
|
|
if (initializer != null) {
|
|
|
|
|
Object resolved = resolve(initializer, argsMap);
|
|
|
|
|
if (resolved != null) argsMap.put(fragment.getName().getIdentifier(), resolved);
|
|
|
|
|
transitions.addAll(parseTransitionsInExpression(initializer, searchStartClass, visitedMethods, argsMap));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (stmt instanceof EnhancedForStatement efs) transitions.addAll(parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, true, null, null));
|
|
|
|
|
else if (stmt instanceof TryStatement ts) transitions.addAll(parseTransitionsInBlock(ts.getBody(), searchStartClass, visitedMethods, argsMap));
|
|
|
|
|
}
|
|
|
|
|
return transitions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void parseStatesInBlock(Block block, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
private void parseStatesInBlock(Block block, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
for (Object stmt : block.statements()) {
|
|
|
|
|
if (stmt instanceof ExpressionStatement es) parseStatesInExpression(es.getExpression(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
else if (stmt instanceof VariableDeclarationStatement vds) {
|
|
|
|
|
for (Object fragmentObj : vds.fragments()) {
|
|
|
|
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
|
|
|
|
Expression initializer = fragment.getInitializer();
|
|
|
|
|
if (initializer != null) {
|
|
|
|
|
Object resolved = resolve(initializer, argsMap);
|
|
|
|
|
if (resolved != null) argsMap.put(fragment.getName().getIdentifier(), resolved);
|
|
|
|
|
parseStatesInExpression(initializer, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (stmt instanceof EnhancedForStatement efs) parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
|
|
|
|
else if (stmt instanceof TryStatement ts) parseStatesInBlock(ts.getBody(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void parseStatesInExpression(Expression expr, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
private void parseStatesInExpression(Expression expr, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
if (expr instanceof MethodInvocation mi) {
|
|
|
|
|
if (isWithStatesChain(mi)) parseStateChain(mi, initialStates, endStates, argsMap);
|
|
|
|
|
if (isWithStatesChain(mi, argsMap)) parseStateChain(mi, initialStates, endStates, argsMap);
|
|
|
|
|
else if (isForEach(mi)) parseForEach(mi, searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
|
|
|
|
else {
|
|
|
|
|
// Check if it's a call on a lambda parameter or a variable that resolved to a lambda
|
|
|
|
|
Expression receiver = mi.getExpression();
|
|
|
|
|
if (receiver instanceof SimpleName sn) {
|
|
|
|
|
Object resolved = argsMap.get(sn.getIdentifier());
|
|
|
|
|
if (resolved instanceof LambdaExpression lambda) {
|
|
|
|
|
parseLambdaInvocation(lambda, mi.arguments(), searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
|
|
|
|
if (calledMethod != null) {
|
|
|
|
|
Map<String, Object> nextArgsMap = buildArgsMap(calledMethod, mi, argsMap);
|
|
|
|
|
@@ -186,7 +265,7 @@ public class StateMachineAggregator {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, Object> buildArgsMap(MethodDeclaration method, MethodInvocation call, Map<String, Object> currentArgsMap) {
|
|
|
|
|
Map<String, Object> map = new HashMap<>();
|
|
|
|
|
Map<String, Object> map = new HashMap<>(currentArgsMap);
|
|
|
|
|
List<?> params = method.parameters();
|
|
|
|
|
List<?> args = call.arguments();
|
|
|
|
|
|
|
|
|
|
@@ -196,24 +275,41 @@ public class StateMachineAggregator {
|
|
|
|
|
if (param.isVarargs() && i < params.size()) {
|
|
|
|
|
List<Expression> varargList = new ArrayList<>();
|
|
|
|
|
for (int j = i; j < args.size(); j++) {
|
|
|
|
|
varargList.add(resolveExpression((Expression) args.get(j), currentArgsMap));
|
|
|
|
|
Object resolved = resolve( (Expression) args.get(j), currentArgsMap);
|
|
|
|
|
if (resolved instanceof List<?> list) {
|
|
|
|
|
for (Object item : list) {
|
|
|
|
|
if (item instanceof Expression e) varargList.add(e);
|
|
|
|
|
}
|
|
|
|
|
} else if (resolved instanceof Expression e) {
|
|
|
|
|
varargList.add(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
map.put(paramName, varargList);
|
|
|
|
|
} else if (i < args.size()) {
|
|
|
|
|
map.put(paramName, resolveExpression((Expression) args.get(i), currentArgsMap));
|
|
|
|
|
map.put(paramName, resolve((Expression) args.get(i), currentArgsMap));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression resolveExpression(Expression expr, Map<String, Object> argsMap) {
|
|
|
|
|
private Object resolve(Expression expr, Map<String, Object> argsMap) {
|
|
|
|
|
if (expr instanceof NullLiteral) return null;
|
|
|
|
|
if (expr instanceof SimpleName sn) {
|
|
|
|
|
Object resolved = argsMap.get(sn.getIdentifier());
|
|
|
|
|
if (resolved instanceof Expression e) return e;
|
|
|
|
|
if (resolved instanceof Expression nextExpr && nextExpr != expr) {
|
|
|
|
|
return resolve(nextExpr, argsMap);
|
|
|
|
|
}
|
|
|
|
|
if (resolved != null) return resolved;
|
|
|
|
|
}
|
|
|
|
|
return expr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Expression resolveExpression(Expression expr, Map<String, Object> argsMap) {
|
|
|
|
|
Object resolved = resolve(expr, argsMap);
|
|
|
|
|
if (resolved instanceof Expression e) return e;
|
|
|
|
|
return expr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, Expression> convertToExpressionMap(Map<String, Object> argsMap) {
|
|
|
|
|
Map<String, Expression> map = new HashMap<>();
|
|
|
|
|
for (Map.Entry<String, Object> entry : argsMap.entrySet()) {
|
|
|
|
|
@@ -222,10 +318,23 @@ public class StateMachineAggregator {
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isTransitionChain(MethodInvocation mi) {
|
|
|
|
|
private boolean isTransitionChainEntry(MethodInvocation mi) {
|
|
|
|
|
String name = mi.getName().getIdentifier();
|
|
|
|
|
if (Set.of("withExternal", "withInternal", "withLocal", "withFork", "withJoin", "withChoice", "withJunction").contains(name)) return true;
|
|
|
|
|
return mi.getExpression() instanceof MethodInvocation next && isTransitionChain(next);
|
|
|
|
|
Expression receiver = mi.getExpression();
|
|
|
|
|
if (receiver instanceof MethodInvocation next) return isTransitionChainEntry(next);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isTransitionChainContinuation(MethodInvocation mi, Map<String, Object> argsMap) {
|
|
|
|
|
String name = mi.getName().getIdentifier();
|
|
|
|
|
if (Set.of("and", "source", "target", "event", "first", "then", "last").contains(name)) {
|
|
|
|
|
Expression receiver = mi.getExpression();
|
|
|
|
|
if (receiver instanceof MethodInvocation next) return isTransitionChainContinuation(next, argsMap) || isTransitionChainEntry(next);
|
|
|
|
|
Object resolved = resolve(receiver, argsMap);
|
|
|
|
|
return resolved instanceof MethodInvocation || resolved == null; // null means it's a parameter we track
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private MethodDeclaration findMethodInHierarchy(String methodName, TypeDeclaration td) {
|
|
|
|
|
@@ -251,43 +360,55 @@ public class StateMachineAggregator {
|
|
|
|
|
return ((SingleVariableDeclaration) method.parameters().get(0)).getType().toString().startsWith("StateMachineTransitionConfigurer");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void aggregateStates(TypeDeclaration startClass, Set<String> initialStates, Set<String> endStates) {
|
|
|
|
|
@Getter
|
|
|
|
|
private final Set<String> initialStates = new HashSet<>();
|
|
|
|
|
@Getter
|
|
|
|
|
private final Set<String> endStates = new HashSet<>();
|
|
|
|
|
|
|
|
|
|
public void aggregateStates(TypeDeclaration startClass) {
|
|
|
|
|
aggregateStatesRecursive(startClass, startClass, initialStates, endStates, new HashSet<>(), new HashSet<>(), new HashMap<>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void aggregateStatesRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
private void aggregateStatesRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<String> visitedClasses, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
|
|
|
|
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
|
|
|
|
|
|
|
|
|
MethodDeclaration configureMethod = findConfigureStatesMethod(currentTd);
|
|
|
|
|
if (configureMethod != null) parseStatesWithMethodCalls(configureMethod, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
|
|
|
|
|
// Hierarchy - Superclass
|
|
|
|
|
Type superclassType = currentTd.getSuperclassType();
|
|
|
|
|
if (superclassType != null) {
|
|
|
|
|
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
|
|
|
|
if (superclassTd != null) aggregateStatesRecursive(superclassTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void parseStatesWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
if (visitedMethods.contains(method)) return;
|
|
|
|
|
visitedMethods.add(method);
|
|
|
|
|
Block body = method.getBody();
|
|
|
|
|
if (body == null) return;
|
|
|
|
|
|
|
|
|
|
for (Object stmt : body.statements()) {
|
|
|
|
|
if (stmt instanceof ExpressionStatement es) parseStatesInExpression(es.getExpression(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
else if (stmt instanceof VariableDeclarationStatement vds) {
|
|
|
|
|
for (Object fragmentObj : vds.fragments()) {
|
|
|
|
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) parseStatesInExpression(fragment.getInitializer(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
} else if (stmt instanceof EnhancedForStatement efs) parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
|
|
|
|
// Hierarchy - Interfaces
|
|
|
|
|
for (Object interfaceTypeObj : currentTd.superInterfaceTypes()) {
|
|
|
|
|
if (interfaceTypeObj instanceof Type interfaceType) {
|
|
|
|
|
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType));
|
|
|
|
|
if (interfaceTd != null) aggregateStatesRecursive(interfaceTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isWithStatesChain(MethodInvocation mi) {
|
|
|
|
|
private void parseStatesWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
|
|
|
|
MethodVisit visit = new MethodVisit(method, new HashMap<>(argsMap));
|
|
|
|
|
if (visitedMethods.contains(visit)) return;
|
|
|
|
|
visitedMethods.add(visit);
|
|
|
|
|
Block body = method.getBody();
|
|
|
|
|
if (body == null) return;
|
|
|
|
|
|
|
|
|
|
parseStatesInBlock(body, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isWithStatesChain(MethodInvocation mi, Map<String, Object> argsMap) {
|
|
|
|
|
if (mi.getName().getIdentifier().equals("withStates")) return true;
|
|
|
|
|
return mi.getExpression() instanceof MethodInvocation next && isWithStatesChain(next);
|
|
|
|
|
Expression receiver = mi.getExpression();
|
|
|
|
|
if (receiver instanceof MethodInvocation next) return isWithStatesChain(next, argsMap);
|
|
|
|
|
Object resolved = resolve(receiver, argsMap);
|
|
|
|
|
if (resolved instanceof MethodInvocation next) return isWithStatesChain(next, argsMap);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void parseStateChain(MethodInvocation mi, Set<String> initialStates, Set<String> endStates, Map<String, Object> argsMap) {
|
|
|
|
|
|