up
This commit is contained in:
@@ -120,26 +120,73 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "CONFIG".length());
|
cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "CONFIG".length());
|
||||||
}
|
}
|
||||||
|
|
||||||
String constraintUpper = constraint.toUpperCase();
|
String expr = constraint;
|
||||||
|
|
||||||
java.util.List<String> allMachines = java.util.List.of("ORDER", "DOCUMENT", "USER");
|
java.util.List<String> allMachines = java.util.List.of("ORDER", "DOCUMENT", "USER");
|
||||||
for (String m : allMachines) {
|
for (String m : allMachines) {
|
||||||
if (!m.equals(cleanMachine)) {
|
boolean isCurrent = m.equals(cleanMachine);
|
||||||
if (constraintUpper.contains(m)) {
|
String replacement = isCurrent ? "true" : "false";
|
||||||
int idx = constraintUpper.indexOf(m);
|
|
||||||
String sub = constraintUpper.substring(0, idx);
|
String regex1 = "(?i)[\"']?" + m + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)";
|
||||||
String cleanedSub = sub.replaceAll("[\\s\"(]+$", "");
|
String regex2 = "(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?" + m + "[\"']?\\s*\\)";
|
||||||
boolean isNegated = cleanedSub.endsWith("!") || cleanedSub.endsWith("NOT") || constraintUpper.contains("!=" + m) || constraintUpper.contains("! = " + m);
|
String regex3 = "(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + m + "[\"']?";
|
||||||
if (!isNegated) {
|
String regex4 = "(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + m + "[\"']?";
|
||||||
if (constraintUpper.contains("\"" + m + "\"") || constraintUpper.contains("'" + m + "'") || constraintUpper.contains(m + ".")) {
|
|
||||||
return false;
|
expr = expr.replaceAll(regex1, replacement);
|
||||||
|
expr = expr.replaceAll(regex2, replacement);
|
||||||
|
expr = expr.replaceAll(regex3, replacement);
|
||||||
|
expr = expr.replaceAll(regex4, isCurrent ? "false" : "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return BooleanEvaluator.evaluate(expr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return !expr.contains("false");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class BooleanEvaluator {
|
||||||
|
public static boolean evaluate(String expr) {
|
||||||
|
expr = expr.replaceAll("\\s+", "");
|
||||||
|
return parseExpr(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean parseExpr(String s) {
|
||||||
|
if (s.isEmpty()) return true;
|
||||||
|
|
||||||
|
int parenDepth = 0;
|
||||||
|
for (int i = s.length() - 1; i >= 0; i--) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
if (c == ')') parenDepth++;
|
||||||
|
else if (c == '(') parenDepth--;
|
||||||
|
else if (parenDepth == 0 && i > 0 && s.charAt(i) == '|' && s.charAt(i - 1) == '|') {
|
||||||
|
return parseExpr(s.substring(0, i - 1)) || parseExpr(s.substring(i + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parenDepth = 0;
|
||||||
|
for (int i = s.length() - 1; i >= 0; i--) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
if (c == ')') parenDepth++;
|
||||||
|
else if (c == '(') parenDepth--;
|
||||||
|
else if (parenDepth == 0 && i > 0 && s.charAt(i) == '&' && s.charAt(i - 1) == '&') {
|
||||||
|
return parseExpr(s.substring(0, i - 1)) && parseExpr(s.substring(i + 1));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.startsWith("!")) {
|
||||||
|
return !parseExpr(s.substring(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.startsWith("(") && s.endsWith(")")) {
|
||||||
|
return parseExpr(s.substring(1, s.length() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.equalsIgnoreCase("true")) return true;
|
||||||
|
if (s.equalsIgnoreCase("false")) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String simplify(String name) {
|
private String simplify(String name) {
|
||||||
if (name == null) return null;
|
if (name == null) return null;
|
||||||
|
|||||||
@@ -408,4 +408,83 @@ class TransitionLinkerEnricherTest {
|
|||||||
assertThat(resComp.getMetadata().getCallChains().get(2).getMatchedTransitions()).isNullOrEmpty(); // Groc
|
assertThat(resComp.getMetadata().getCallChains().get(2).getMatchedTransitions()).isNullOrEmpty(); // Groc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleMultipleNegations() {
|
||||||
|
Transition t1 = new Transition();
|
||||||
|
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
|
||||||
|
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
|
||||||
|
t1.setEvent(Event.of("PAY", "PAY"));
|
||||||
|
|
||||||
|
// Case 1: Double Negation on ORDER -> !(!(ORDER)) -> Should act as POSITIVE (meaning order is required).
|
||||||
|
// Since we are running OrderStateMachineConfiguration, it should match!
|
||||||
|
CallChain chain1 = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.constraint("!(!(ORDER.equals(type)))")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult resultOrder = AnalysisResult.builder()
|
||||||
|
.name("com.example.OrderStateMachineConfiguration")
|
||||||
|
.transitions(List.of(t1))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain1)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(resultOrder, null, null);
|
||||||
|
assertThat(resultOrder.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
||||||
|
|
||||||
|
// Case 2: Triple Negation on ORDER -> !(!(!(ORDER))) -> Should act as NEGATIVE (meaning order is rejected/forbidden).
|
||||||
|
// Under OrderStateMachineConfiguration, this chain should be rejected!
|
||||||
|
CallChain chain2 = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.constraint("!(!(!(ORDER.equals(type))))")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult resultOrder2 = AnalysisResult.builder()
|
||||||
|
.name("com.example.OrderStateMachineConfiguration")
|
||||||
|
.transitions(List.of(t1))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain2)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(resultOrder2, null, null);
|
||||||
|
assertThat(resultOrder2.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleComplexBooleanLogic() {
|
||||||
|
Transition t1 = new Transition();
|
||||||
|
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
|
||||||
|
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
|
||||||
|
t1.setEvent(Event.of("PAY", "PAY"));
|
||||||
|
|
||||||
|
// Case 1: OR expression -> "ORDER".equals(type) || "DOCUMENT".equals(type)
|
||||||
|
// Under OrderStateMachineConfiguration, it should match!
|
||||||
|
CallChain chain1 = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.constraint("\"ORDER\".equalsIgnoreCase(type) || \"DOCUMENT\".equalsIgnoreCase(type)")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult resultOrder = AnalysisResult.builder()
|
||||||
|
.name("com.example.OrderStateMachineConfiguration")
|
||||||
|
.transitions(List.of(t1))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain1)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(resultOrder, null, null);
|
||||||
|
assertThat(resultOrder.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
||||||
|
|
||||||
|
// Case 2: Under UserStateMachineConfiguration, it should be rejected!
|
||||||
|
AnalysisResult resultUser = AnalysisResult.builder()
|
||||||
|
.name("com.example.UserStateMachineConfiguration")
|
||||||
|
.transitions(List.of(t1))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain1)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(resultUser, null, null);
|
||||||
|
assertThat(resultUser.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user