Compare commits
42 Commits
382a221a62
...
ai-branch-
| Author | SHA1 | Date | |
|---|---|---|---|
| bb97285906 | |||
| 9a5f122bd2 | |||
| a7bf07e8b2 | |||
| 4f8c35adef | |||
| 31adae0a4b | |||
| 2caaec5419 | |||
| 56588a835d | |||
| 0a8f9720c9 | |||
| 6472f01026 | |||
| e041cb9a80 | |||
| 9bea5c4687 | |||
| 475084aed4 | |||
| 28df9fc99f | |||
| e3dd26c0d4 | |||
| 89736a4aa1 | |||
| 092fbe49fa | |||
| 61f5549589 | |||
| f5067490a3 | |||
| 3a442c8890 | |||
| 7807df57ca | |||
| 82719489aa | |||
| c390f02389 | |||
| 180a283383 | |||
| 1c852005c8 | |||
| 34c53b1097 | |||
| 23af434504 | |||
| cc352432a4 | |||
| 78c0f3e705 | |||
| 27005da678 | |||
| 0cb78e5dfd | |||
| bcdbbffa13 | |||
| 3988864494 | |||
| b07b7855a1 | |||
| baa33a887c | |||
| 0165da5f51 | |||
| 4d7cda56f5 | |||
| 2717a01f13 | |||
| f84043f26e | |||
| 4d9fee716b | |||
| f3d030b19a | |||
| 16dccbf81b | |||
| 20b4ed780b |
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;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
## Phase 11: Formal Compiler & Static Analysis Alignment (Limitations & Missing Features)
|
||||
While the custom AST-based `JdtDataFlowModel` supports advanced OOP data flow (like flow-sensitive local mutations, fluent builders, and polymorphic union fallbacks), the following compiler/static analysis behaviors are missing or simplified:
|
||||
|
||||
1. **Full Pointer & Alias Analysis**:
|
||||
- If two variables alias the same object (e.g., `Command cmd1 = cmd2;`), a mutation on `cmd1` (e.g., `cmd1.setEvent("A")`) is not reflected when reading the fields of `cmd2` unless they resolve to the exact same AST node reference. A formal pointer/alias analysis (like Anderson's or Steensgaard's algorithm) would map abstract heap locations rather than AST nodes.
|
||||
|
||||
2. **Advanced Loop Fixpoints for Fields**:
|
||||
- If a loop mutates a field based on its previous values (e.g., `while(cond) { builder.count(builder.getCount() + 1); }`), a simple sequential pass will miss the cyclic dependency. A formal data flow framework uses a mathematical lattice and runs a fixpoint iteration algorithm (using meet/join operators) to compute the stable approximation of values.
|
||||
|
||||
3. **Exception Path Analysis (Exception CFG)**:
|
||||
- Edges for exception flows (`throw` statements, `try-catch-finally` blocks, and implicit unchecked runtime exceptions like `NullPointerException`) are not modeled. Consequently, mutations or return paths within catch/finally blocks may be missed or evaluated out of order.
|
||||
|
||||
4. **Collection & Array Tracking**:
|
||||
- Arrays and Collections (such as `List`, `Map`, `Set`) are treated as opaque objects, and their operations (like `list.add(val)` or `map.put(key, val)`) are evaluated as general method calls. Modifying a collection does not propagate the values to its getter calls (e.g. `list.get(0)`).
|
||||
|
||||
5. **Field Shadowing and Inheritance Semantics**:
|
||||
- If a subclass shadows a superclass field (declaring a field with the same name), our resolver might resolve the wrong field binding. Also, implicit superclass constructors and field initializers from superclass hierarchies are not fully simulated.
|
||||
|
||||
6. **Static Initialization Blocks and Static Mutable State**:
|
||||
- Class loading execution order (i.e. static block initializations `static { ... }`) is not modeled. A mutation on a static field in one method is not propagated to reads in another method.
|
||||
|
||||
7. **Type Erasure & Generics Precision**:
|
||||
- Generics type bounds (e.g., wildcards `? extends T`, type parameter replacements) are not fully tracked, which can result in incorrect polymorphic resolution when method signatures differ slightly from erased bindings.
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# Advanced Context Extraction: Payloads and Interceptors
|
||||
|
||||
## 1. Incoming Payload Analysis
|
||||
To understand *what* data drives the state machine, we need to capture metadata about the incoming parameters at entry points.
|
||||
|
||||
### REST Endpoints
|
||||
For methods annotated with `@PostMapping`, `@PutMapping`, etc.:
|
||||
1. **Extract Parameters**: Scan method parameters for `@RequestBody`, `@PathVariable`, or `@RequestParam`.
|
||||
2. **Type Extraction**: Capture the FQN of the parameter type (e.g., `com.example.OrderRequest`).
|
||||
3. **Data Linkage**: If a field from this payload is used in a `sendEvent(payload.getEvent())` call, mark the transition as "Dynamic based on Payload".
|
||||
|
||||
### Message Listeners
|
||||
For `@RabbitListener` or `@JmsListener`:
|
||||
1. Identify the parameter receiving the message body.
|
||||
2. Extract its type and any `@Header` or `@Payload` annotations.
|
||||
|
||||
## 2. Interceptor and Filter Analysis
|
||||
Interceptors often perform cross-cutting logic (authentication, context injection) that affects how state machines are driven.
|
||||
|
||||
### Discovery Strategy
|
||||
1. **Find Interceptors**: Scan for classes implementing `HandlerInterceptor` or `WebRequestInterceptor`.
|
||||
2. **Registration Mapping**: Look for `WebMvcConfigurer.addInterceptors()` calls to identify which URL patterns each interceptor applies to.
|
||||
3. **Context Injection**: Analyze if the interceptor modifies the request (e.g., `request.setAttribute("userContext", ...)`) or populates a `ThreadLocal` (like `SecurityContextHolder`).
|
||||
|
||||
### Linking to State Machines
|
||||
If a controller uses a value from a known Interceptor-injected context to decide which event to send or which state machine to restore, we can link the Interceptor's logic to the State Machine flow.
|
||||
|
||||
## 3. Implementation in `EnrichmentService`
|
||||
We will introduce a `ContextEnricher` that:
|
||||
1. Builds a map of **Active Interceptors** per path.
|
||||
2. Augments `EntryPoint` metadata with **Payload Type** information.
|
||||
|
||||
## 4. Modeling Updates
|
||||
Update `EntryPoint` and `TriggerPoint` to include `payloadType` and `interceptors`.
|
||||
|
||||
```java
|
||||
public record EntryPoint(
|
||||
// ... existing fields ...
|
||||
String payloadType,
|
||||
List<String> activeInterceptors
|
||||
) {}
|
||||
```
|
||||
@@ -1,63 +0,0 @@
|
||||
# Decoupled Analysis Architecture: The "Modular Provider" Approach
|
||||
|
||||
## Core Philosophy
|
||||
We decouple the **State Machine Definition** (what the machine IS) from the **Codebase Intelligence** (how the machine IS USED). This allows for swappable analysis tools and prevents the state machine parser from becoming a monolithic "all-knowing" beast.
|
||||
|
||||
## 1. The Intelligence Interface (The Bridge)
|
||||
We introduce an abstraction layer that provides information about the code without tying us to a specific implementation (like JDT).
|
||||
|
||||
```java
|
||||
public interface CodebaseIntelligenceProvider {
|
||||
// Event Triggers
|
||||
List<TriggerPoint> findTriggerPoints();
|
||||
|
||||
// Entry Points
|
||||
List<EntryPoint> findEntryPoints();
|
||||
|
||||
// Call Graphs
|
||||
List<CallChain> traceCallChain(MethodReference target);
|
||||
|
||||
// Resolution
|
||||
String resolveValue(ExpressionReference ref);
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Swappable Implementations
|
||||
This allows us to offer different "tiers" of analysis or even different tools:
|
||||
- **JdtProvider (Current)**: High-precision, understands hierarchy, but heavy.
|
||||
- **RegexProvider**: Lightweight, fast, good for simple projects or quick scans.
|
||||
- **ExternalLspProvider (Future)**: Could query a running Language Server (like Eclipse JDT.LS) for ultra-accurate cross-project resolution.
|
||||
|
||||
## 3. The "State Machine Model" is the Sink
|
||||
The State Machine Exporter now becomes a "Consumer" of intelligence.
|
||||
1. **SM Parser**: Extracts the core states and transitions.
|
||||
2. **Intelligence Service**: Discovers where events are triggered.
|
||||
3. **Correlation Engine**: Merges the two models based on Event IDs.
|
||||
|
||||
## 4. Benefits
|
||||
- **Testability**: We can mock the `CodebaseIntelligenceProvider` to test state machine rendering without needing a full Java project on disk.
|
||||
- **Maintainability**: Adding support for a new framework (e.g., Micronaut) only requires updating the `IntelligenceProvider`, not touching the core state machine logic.
|
||||
- **Performance**: We can run the SM Parser and the Intelligence Scanner in parallel since they are now independent.
|
||||
|
||||
## 5. Monorepo and Multi-Module Support
|
||||
Applications are often split into multiple modules (e.g., `core`, `api`, `workers`).
|
||||
- **Workspace Scanning**: The analyzer should treat the entire monorepo as a single codebase context.
|
||||
- **Source Tracking**: Each metadata item (`TriggerPoint`, `EntryPoint`) includes a `sourceModule` identifier to show exactly where it was found.
|
||||
- **Cross-Module Resolution**: Properties defined in one module's `application.yml` and used in another should be resolved globally.
|
||||
- **Internal Dependency Following**: If the analyzer finds a call to a method in another module, it should continue the call-chain analysis into that module's source.
|
||||
|
||||
## Challenges
|
||||
- **Multiple State Machines**: How to know which `StateMachine` instance is being used?
|
||||
- Initial heuristic: If there's only one, assume it's that one.
|
||||
- If multiple, check generic types `StateMachine<S, E>` or variable names.
|
||||
- **Indirect Calls**: Method `A` (Controller) calls `B` (Service), and `B` calls `sendEvent`.
|
||||
- Static analysis might need to follow call graphs.
|
||||
- Use JDT's cross-reference capabilities if possible, or build a simple call graph.
|
||||
- **Inheritance**:
|
||||
- Controllers might extend base classes with common mappings.
|
||||
- State machine configurations already handle inheritance; Trigger detection should too.
|
||||
|
||||
## Next Steps
|
||||
1. Create a PoC `TriggerDetector` for Spring MVC.
|
||||
2. Integrate `TriggerAggregator` into the main analysis pipeline.
|
||||
3. Update the `Exporter` to visualize these links.
|
||||
@@ -1,40 +0,0 @@
|
||||
# The Analysis Contract: Decoupling Metadata from Implementation
|
||||
|
||||
## 1. Unified Model
|
||||
Instead of each parser having its own internal model, we move to a "Context-Rich Model".
|
||||
|
||||
```java
|
||||
public record CodebaseMetadata(
|
||||
List<TriggerPoint> triggers,
|
||||
List<EntryPoint> entryPoints,
|
||||
Map<String, String> properties,
|
||||
CallGraph callGraph
|
||||
) {}
|
||||
```
|
||||
|
||||
## 2. Intelligence Provider Lifecycle
|
||||
1. **Initialize**: Provide root paths and configuration.
|
||||
2. **Scan**: The provider builds its internal index (e.g., JDT AST, Regex indices).
|
||||
3. **Extract**: The provider returns a `CodebaseMetadata` object.
|
||||
4. **Dispose**: Cleanup resources.
|
||||
|
||||
## 3. Tool-Specific Strengths
|
||||
|
||||
### JDT Provider (Advanced)
|
||||
- **Strengths**: True semantic understanding, inheritance resolution, precise constant lookups.
|
||||
- **Usage**: Deep analysis where we need to know that `BaseService.notify()` is actually what is being called in `ChildService`.
|
||||
|
||||
### Regex / Tree-Sitter Provider (Light)
|
||||
- **Strengths**: Extremely fast, works even on broken code (doesn't need full classpath), very low memory overhead.
|
||||
- **Usage**: Large monorepos where full JDT analysis would take minutes.
|
||||
|
||||
## 4. Aggregation Logic
|
||||
The `IntelligenceService` can actually combine results from multiple providers!
|
||||
- Use JDT for the State Machine Config (where we need high precision).
|
||||
- Use Regex for a quick scan of 1000+ controllers to find mapping patterns.
|
||||
|
||||
## 5. Refactoring Strategy
|
||||
1. **Extract**: Move current JDT-based transition parsing into a `JdtStateMachineProvider`.
|
||||
2. **Define**: Create the `CodebaseMetadata` model.
|
||||
3. **Bridge**: Update the `ExportService` to accept both a `StateMachineModel` and a `CodebaseMetadata` object.
|
||||
4. **Implement**: Create the `CodebaseIntelligenceProvider` interface and its first implementation (wrapping the existing JDT logic).
|
||||
@@ -1,67 +0,0 @@
|
||||
# Low-Impact Abstraction: The Enrichment Pipeline
|
||||
|
||||
## 1. Goal
|
||||
Support enhanced codebase analysis (Endpoints, Listeners, etc.) without a massive refactor of the existing JDT state machine parsers.
|
||||
|
||||
## 2. The Core Model: `AnalysisResult`
|
||||
We wrap the existing output into a container that can hold optional metadata.
|
||||
|
||||
```java
|
||||
public class AnalysisResult {
|
||||
// Current Core Data
|
||||
private final String name;
|
||||
private final List<Transition> transitions;
|
||||
private final Set<String> startStates;
|
||||
private final Set<String> endStates;
|
||||
|
||||
// Optional Enhanced Data
|
||||
private CodebaseMetadata metadata = CodebaseMetadata.empty();
|
||||
|
||||
public AnalysisResult(String name, List<Transition> transitions, Set<String> startStates, Set<String> endStates) {
|
||||
this.name = name;
|
||||
this.transitions = transitions;
|
||||
this.startStates = startStates;
|
||||
this.endStates = endStates;
|
||||
}
|
||||
|
||||
public void setMetadata(CodebaseMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. The "Enricher" Abstraction
|
||||
Instead of modifying the parser, we "enrich" the result after it's produced.
|
||||
|
||||
```java
|
||||
public interface AnalysisEnricher {
|
||||
/**
|
||||
* Examines the core analysis result and the codebase context
|
||||
* to add extra metadata (triggers, endpoints, etc.).
|
||||
*/
|
||||
void enrich(AnalysisResult result, CodebaseContext context);
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Implementation Tiers
|
||||
|
||||
### Tier 1: Current State (No-Op)
|
||||
The `ExportService` calls `enrich()`, but since no enrichers are registered, it just returns the core data. This is zero-impact refactoring.
|
||||
|
||||
### Tier 2: Enhanced Analysis
|
||||
Once we implement `SpringMvcEnricher` or `RabbitMqEnricher`, we register them. They will:
|
||||
1. Scan the `CodebaseContext`.
|
||||
2. Find `TriggerPoints`.
|
||||
3. Link them to the `transitions` already present in the `AnalysisResult`.
|
||||
4. Update the `metadata` field.
|
||||
|
||||
## 5. Why this is "Good":
|
||||
- **Non-Breaking**: `StateMachineAggregator` remains untouched.
|
||||
- **Pluggable**: You can add/remove "Intelligence" without changing the core parser.
|
||||
- **Future-Proof**: If you switch from JDT to another tool for code analysis, you only change the `Enricher` implementation.
|
||||
|
||||
## 6. Refactoring Steps
|
||||
1. **Create `AnalysisResult`** and **`CodebaseMetadata`** classes.
|
||||
2. **Update `ExportService.generateOutputs`** to take an `AnalysisResult` object instead of 4 separate parameters.
|
||||
3. **Introduce `EnrichmentService`** that holds a list of `AnalysisEnricher`s.
|
||||
4. **Modify `ExportService`** to call `enrichmentService.enrich(result, context)` before passing the result to exporters.
|
||||
@@ -1,92 +0,0 @@
|
||||
# Generic and Extensible Endpoint Finder
|
||||
|
||||
## The Need for Genericity
|
||||
Applications use various frameworks and patterns to trigger state machine events. While Spring MVC and Kafka are common, others like Micronuat, Jakarta EE, or custom internal event buses might be used.
|
||||
|
||||
## Configuration-Driven Detection
|
||||
We can allow users to define "Entry Point" patterns via a configuration file (e.g., `analysis-config.json`).
|
||||
|
||||
```json
|
||||
{
|
||||
"entryPoints": [
|
||||
{
|
||||
"name": "SpringMVC",
|
||||
"classAnnotations": ["RestController", "Controller"],
|
||||
"methodAnnotations": ["PostMapping", "GetMapping", "PutMapping", "DeleteMapping", "RequestMapping"],
|
||||
"metadataExtractors": {
|
||||
"path": ["value", "path"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Kafka",
|
||||
"methodAnnotations": ["KafkaListener"],
|
||||
"metadataExtractors": {
|
||||
"topic": ["topics", "topicPartitions"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Abstract EntryPoint Detector
|
||||
A generic detector that uses the above configuration to identify entry points.
|
||||
|
||||
```java
|
||||
public class ConfigurableTriggerDetector implements TriggerDetector {
|
||||
private final AnalysisConfig config;
|
||||
|
||||
@Override
|
||||
public List<TriggerPoint> detect(CompilationUnit cu, CodebaseContext context) {
|
||||
// 1. Find all classes
|
||||
// 2. If class has any of config.classAnnotations
|
||||
// 3. Scan methods for any of config.methodAnnotations
|
||||
// 4. If found, look for sendEvent calls inside the method or its call tree
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Indirect Flow Detection (Call Tree Analysis)
|
||||
Oftentimes, the `sendEvent` call is not directly in the controller method but in a service called by the controller.
|
||||
|
||||
### Simple Call Tree Analysis
|
||||
1. Find all methods that call `sendEvent(E)`. Let's call them "Direct Triggers".
|
||||
2. Find all methods that call "Direct Triggers".
|
||||
3. Recursively build this "Trigger Path".
|
||||
4. If a "Trigger Path" starts at an "Entry Point" (Controller/Listener), we have a complete flow.
|
||||
|
||||
Example:
|
||||
`OrderController.submitOrder()` -> `OrderService.processOrder()` -> `StateMachine.sendEvent(SUBMIT)`
|
||||
|
||||
Analysis:
|
||||
1. `OrderService.processOrder()` is a Direct Trigger for `SUBMIT`.
|
||||
2. `OrderController.submitOrder()` calls `OrderService.processOrder()`.
|
||||
3. `OrderController.submitOrder()` is an Entry Point (annotated with `@PostMapping`).
|
||||
4. Result: `POST /order` -> `SUBMIT` transition.
|
||||
|
||||
## Handling Reactive Chains Statically
|
||||
In WebFlux/Project Reactor, the logic is decoupled into a pipeline. Static analysis must "connect the dots" across operators.
|
||||
|
||||
**Strategy**:
|
||||
1. When analyzing a method, identify if it returns a reactive type (`Mono`, `Flux`).
|
||||
2. If so, inspect all lambdas within that method.
|
||||
3. If a lambda calls `sendEvent`, treat the method as a "Trigger Point".
|
||||
4. If the method is also an "Entry Point" (via annotations or `RouterFunction`), we have a direct reactive flow.
|
||||
|
||||
## Supporting Inheritance in Listeners
|
||||
Often, base classes or interfaces define generic listeners:
|
||||
```java
|
||||
public interface BaseConsumer<T> {
|
||||
@RabbitListener(queues = "${app.queue}")
|
||||
void handle(T payload);
|
||||
}
|
||||
```
|
||||
The `CodebaseContext` must resolve the `${app.queue}` if possible (via `application.properties` scanning) or at least note that it's a parameterized listener.
|
||||
|
||||
## Advanced Call Graph: The "Data Flow"
|
||||
For a truly detailed analysis, we should track NOT just method calls, but also how the "Event" object is passed around.
|
||||
- Is the event hardcoded? `sm.sendEvent(Events.CREATE)`
|
||||
- Is it derived from a payload field? `sm.sendEvent(payload.getEvent())`
|
||||
- Is it mapped via a `switch` or `Map`?
|
||||
|
||||
**Static Mapping Inference**:
|
||||
If we see a `switch` statement on a payload field that leads to different `sendEvent` calls, we can report multiple possible flows from the same endpoint.
|
||||
@@ -1,102 +0,0 @@
|
||||
# Implementation Details: Trigger Detection with JDT
|
||||
|
||||
## 1. Finding `sendEvent` Calls
|
||||
We can use an `ASTVisitor` to find all `MethodInvocation` nodes.
|
||||
|
||||
```java
|
||||
public class SendEventVisitor extends ASTVisitor {
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if ("sendEvent".equals(node.getName().getIdentifier())) {
|
||||
// Found a call!
|
||||
// 1. Extract event (first argument)
|
||||
// 2. Identify the enclosing method and class
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Challenges in Event Extraction
|
||||
Events can be:
|
||||
- Enum constants: `Events.SUBMIT`
|
||||
- Strings: `"SUBMIT"`
|
||||
- Variables: `sm.sendEvent(eventFromPayload)` (Hard to resolve statically)
|
||||
|
||||
We should reuse `CodebaseContext.resolveState` logic (which is basically resolving an expression to a value/fqn).
|
||||
|
||||
## 2. Identifying Enclosing Context
|
||||
Once a `sendEvent` is found, we can traverse up the AST to find the `MethodDeclaration` and `TypeDeclaration`.
|
||||
|
||||
```java
|
||||
ASTNode parent = node.getParent();
|
||||
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
// parent is now the MethodDeclaration
|
||||
```
|
||||
|
||||
### 2. Extracting Annotations
|
||||
From `MethodDeclaration`, we can check for mappings:
|
||||
- `@PostMapping`, `@GetMapping`, etc.
|
||||
- `@KafkaListener`: Extract `topics`, `groupId`.
|
||||
- `@RabbitListener`: Extract `queues`, `bindings` (Exchange/RoutingKey).
|
||||
|
||||
From `TypeDeclaration`, we can check for:
|
||||
- `@RestController`, `@Controller`
|
||||
- `@RequestMapping` at class level (to get base path)
|
||||
|
||||
## 3. Specialized WebFlux Analysis
|
||||
|
||||
### Annotation-based WebFlux
|
||||
This is largely identical to Spring MVC. The challenge is if the `sendEvent` is wrapped in a reactive operator.
|
||||
```java
|
||||
public Mono<Void> submit(Order order) {
|
||||
return service.save(order)
|
||||
.doOnNext(o -> stateMachine.sendEvent(Events.SUBMIT))
|
||||
.then();
|
||||
}
|
||||
```
|
||||
**Static Strategy**: We need to look inside `LambdaExpression` nodes passed to reactive operators (`doOnNext`, `flatMap`, `map`, `subscribe`). The `ASTVisitor` should traverse into these lambdas.
|
||||
|
||||
### Functional WebFlux (RouterFunctions)
|
||||
Functional endpoints are often defined as Beans returning `RouterFunction`.
|
||||
```java
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> route(OrderHandler handler) {
|
||||
return RouterFunctions.route(POST("/orders"), handler::submitOrder);
|
||||
}
|
||||
```
|
||||
**Static Strategy**:
|
||||
1. Find methods returning `RouterFunction`.
|
||||
2. Analyze the `MethodInvocation` chain (`route`, `andRoute`, `nest`).
|
||||
3. Extract the URI pattern and the `HandlerFunction` reference.
|
||||
4. If the handler is a method reference (`handler::submitOrder`), link it to the corresponding `MethodDeclaration`.
|
||||
|
||||
## 4. Specialized RabbitMQ Analysis
|
||||
`@RabbitListener` can be complex:
|
||||
```java
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(value = "orderQueue", durable = "true"),
|
||||
exchange = @Exchange(value = "orderExchange"),
|
||||
key = "order.created"
|
||||
))
|
||||
public void onOrder(Order order) { ... }
|
||||
```
|
||||
**Static Strategy**:
|
||||
1. Find `@RabbitListener`.
|
||||
2. If it has `bindings`, drill down into `@QueueBinding`, `@Queue`, `@Exchange` to extract the topology.
|
||||
3. If it only has `queues`, resolve the queue name (might be a SpEL expression or property placeholder, which we can try to resolve or just keep as-is).
|
||||
|
||||
## 5. Indirect Flow Detection (The "Service Link")
|
||||
If the project has multiple state machines, we need to know which one is being targeted.
|
||||
Usually, this is done via:
|
||||
- Autowiring by type: `StateMachine<States, Events> sm;`
|
||||
- Autowiring by name: `@Qualifier("mySm") StateMachine sm;`
|
||||
|
||||
We can look at the fields of the class where `sendEvent` is called.
|
||||
|
||||
## 6. Output Enhancement
|
||||
The `Exporter` should be modified to:
|
||||
- In DOT: Add nodes for Endpoints/Listeners and link them to the Events/Transitions.
|
||||
- In SCXML: Add metadata to transitions.
|
||||
@@ -1,73 +0,0 @@
|
||||
# State Machine Instance Identification & Persistence Mapping
|
||||
|
||||
## 1. The Multi-SM Problem
|
||||
In large applications, multiple State Machines often coexist. A "Trigger Point" (like a REST Controller) must be linked to the correct State Machine definition.
|
||||
|
||||
**Common Identification Patterns**:
|
||||
- **Unique Types**: `StateMachine<OrderState, OrderEvent>` vs `StateMachine<UserState, UserEvent>`.
|
||||
- **Bean Qualifiers**: `@Qualifier("orderStateMachine")`.
|
||||
- **Factory IDs**: `factory.getStateMachine("order-123")`.
|
||||
- **Persistence Restoration**: `persister.restore(sm, orderId)`.
|
||||
|
||||
## 2. Analysis Strategy: "Instance Tracking"
|
||||
|
||||
### 1. Definition Discovery (Existing)
|
||||
Identify all configurations (classes with `@EnableStateMachineFactory` or `@EnableStateMachine`). Store their **Bean Names** and **Generic Types**.
|
||||
|
||||
### 2. Dependency Analysis
|
||||
When a class (e.g., `OrderController`) uses a state machine:
|
||||
1. **Identify the Field/Parameter**: Look for `StateMachine<S, E>`.
|
||||
2. **Resolve Generic Types**: Match `S` and `E` against known SM definitions.
|
||||
3. **Resolve Qualifiers**: Check for `@Qualifier` or variable names that match a SM bean name.
|
||||
|
||||
### 3. Loading & Persistence Analysis
|
||||
A dedicated "Loading Detector" will look for persistence logic.
|
||||
|
||||
**Pattern: Persister Restore**
|
||||
```java
|
||||
persister.restore(stateMachine, id);
|
||||
stateMachine.sendEvent(E);
|
||||
```
|
||||
**Static Strategy**:
|
||||
- Find calls to `Persister.restore(sm, ...)` or `PersistStateChangeListener`.
|
||||
- Link the variable `sm` to the restoration event.
|
||||
- Mark the `TriggerPoint` as "Restored from Persistence".
|
||||
|
||||
**Pattern: Factory Creation**
|
||||
```java
|
||||
StateMachine sm = factory.getStateMachine(smId);
|
||||
```
|
||||
**Static Strategy**:
|
||||
- Find `factory.getStateMachine(...)`.
|
||||
- If the argument is a literal (e.g., `"order"`), map it to the SM definition named "order".
|
||||
|
||||
## 3. Implementation: `InstanceIdentifier`
|
||||
We will introduce an `InstanceIdentifier` that works alongside the `ValueResolver`.
|
||||
|
||||
```java
|
||||
public class InstanceIdentifier {
|
||||
public StateMachineReference identify(VariableDeclaration var, CodebaseContext context) {
|
||||
// 1. Check type generics
|
||||
// 2. Check @Qualifier
|
||||
// 3. Trace back to factory or persister calls
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Modeling in `AnalysisResult`
|
||||
The `TriggerPoint` will be enhanced with a `stateMachineId` or `configFqn` field.
|
||||
|
||||
```java
|
||||
public record TriggerPoint(
|
||||
String className,
|
||||
String methodName,
|
||||
String event,
|
||||
String stateMachineId, // Links back to the specific SM
|
||||
boolean isRestoredFromPersistence,
|
||||
Map<String, String> metadata
|
||||
) {}
|
||||
```
|
||||
|
||||
## 5. Challenges
|
||||
- **Generic Controllers**: A single base controller that handles multiple SMs via generics. We might need to report "Multiple Potential SMs".
|
||||
- **Dynamic Factory IDs**: `factory.getStateMachine(payload.getType())`. Hard to resolve statically without data flow analysis.
|
||||
@@ -1,55 +0,0 @@
|
||||
# Polyglot Analysis: Supporting Mixed Java and Kotlin
|
||||
|
||||
## 1. The Need for Language Abstraction
|
||||
Professional JVM codebases are increasingly "Mixed Mode" (Java + Kotlin). Tying our analysis logic directly to JDT (Java AST) creates a bottleneck for Kotlin support.
|
||||
|
||||
## 2. The "Driver" Architecture
|
||||
We decouple the **Intelligence Logic** (e.g., "Find all REST controllers") from the **AST Parser** (e.g., "Walk the JDT tree").
|
||||
|
||||
### 1. Language Driver Interface
|
||||
```java
|
||||
public interface LanguageDriver {
|
||||
boolean supports(Path filePath);
|
||||
|
||||
// Unified Extraction Methods
|
||||
List<RawClassMetadata> extractClasses(Path filePath);
|
||||
List<RawMethodMetadata> extractMethods(RawClassMetadata cls);
|
||||
List<RawInvocationMetadata> findInvocations(RawMethodMetadata method, String targetMethodName);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Unified Metadata Model (Intermediate Representation)
|
||||
We move to a "Generic JVM Model" before performing the final state machine mapping.
|
||||
|
||||
```java
|
||||
public record RawClassMetadata(
|
||||
String fqn,
|
||||
String language, // "java", "kotlin"
|
||||
List<String> annotations,
|
||||
String superclass,
|
||||
List<String> interfaces
|
||||
) {}
|
||||
|
||||
public record RawMethodMetadata(
|
||||
String name,
|
||||
List<String> annotations,
|
||||
String returnType,
|
||||
List<String> parameterTypes
|
||||
) {}
|
||||
```
|
||||
|
||||
## 3. Polyglot Analysis Flow
|
||||
1. **File Discovery**: Find all `.java` and `.kt` files.
|
||||
2. **Driver Assignment**: Delegate each file to the appropriate `LanguageDriver`.
|
||||
3. **Cross-Language Resolution**: The `CodebaseContext` stores metadata for ALL classes.
|
||||
- A Java controller calling a Kotlin service is resolved by looking up the Kotlin class's unified metadata.
|
||||
4. **Enricher Execution**: Enrichers (like `SpringMvcEnricher`) now operate on the **Unified Metadata Model**, making them language-agnostic!
|
||||
|
||||
## 4. Why this is "Future Proof":
|
||||
- **Enrichers are written once**: The logic to find `@PostMapping` only needs to know how to query the `RawClassMetadata` annotations, not how to traverse JDT vs. Kotlin PSI.
|
||||
- **Easy Expansion**: To support Scala, Groovy, or even a newer Java version, you just add a new `LanguageDriver`.
|
||||
|
||||
## 5. Refactoring Plan
|
||||
1. **Define Intermediate Models**: Create `RawClassMetadata` and `RawMethodMetadata`.
|
||||
2. **Extract JDT Logic**: Move the current JDT-specific code into `JavaLanguageDriver`.
|
||||
3. **Kotlin PoC (Phase 12)**: Implement a basic `KotlinLanguageDriver` using Regex or a lightweight parser (like Tree-Sitter) to prove the abstraction works.
|
||||
@@ -1,80 +0,0 @@
|
||||
# Spring Property and Value Resolution Strategy
|
||||
|
||||
## 1. The Challenge of "Injected" Metadata
|
||||
In Spring, metadata (like Queue names, Topic names, or even Event names) is often not hardcoded but injected using various mechanisms. To provide an accurate map, the analyzer must resolve these values.
|
||||
|
||||
## 2. Supported Injection Patterns
|
||||
|
||||
### Field Injection
|
||||
```java
|
||||
@Value("${app.order-queue}")
|
||||
private String orderQueue;
|
||||
```
|
||||
**Static Strategy**:
|
||||
1. Scan all fields for `@Value`.
|
||||
2. Map `FieldName` -> `Placeholder/Value`.
|
||||
3. If `${...}` is found, resolve it via the `PropertyResolver`.
|
||||
|
||||
### Constructor and Setter Injection
|
||||
```java
|
||||
public OrderService(@Value("${app.event.submit}") String submitEvent) {
|
||||
this.submitEvent = submitEvent;
|
||||
}
|
||||
```
|
||||
**Static Strategy**:
|
||||
1. Scan constructor/method parameters for `@Value`.
|
||||
2. Trace the assignment to a class field (`this.submitEvent = ...`).
|
||||
3. Store the mapping for that field.
|
||||
|
||||
### @ConfigurationProperties
|
||||
```java
|
||||
@ConfigurationProperties(prefix = "app.messaging")
|
||||
public class AppProps {
|
||||
private String queueName;
|
||||
}
|
||||
```
|
||||
**Static Strategy**:
|
||||
1. Identify `@ConfigurationProperties` classes.
|
||||
2. Index their fields with the prefix (e.g., `app.messaging.queueName`).
|
||||
3. When these beans are injected into a service, link the service's usage to the resolved property value.
|
||||
|
||||
## 3. Profile-Aware Property Resolver
|
||||
A component dedicated to building a multi-dimensional map of available properties.
|
||||
|
||||
1. **File Scanning**: Parse `application.properties`, `application.yml`, and `bootstrap.yml`.
|
||||
2. **Profile Identification**:
|
||||
- From filenames: `application-{profile}.properties`.
|
||||
- From YAML documents: `spring.config.activate.on-profile` (Spring Boot 2.4+).
|
||||
3. **The Multi-Profile Map**:
|
||||
- Instead of one map, we store: `Map<ProfileName, Map<Key, Value>>`.
|
||||
- The "default" profile is the base.
|
||||
|
||||
## 4. Smart Value Propagation
|
||||
When a trigger uses a property, we want to know it's profile-dependent.
|
||||
|
||||
**Example Trace**:
|
||||
1. `application.yml` -> `app.queue: "orders"`
|
||||
2. `application-prod.yml` -> `app.queue: "orders-prod"`
|
||||
3. `TriggerPoint` stores:
|
||||
- `placeholder: "${app.queue}"`
|
||||
- `defaultResolvedValue: "orders"`
|
||||
4. **Rendering Strategy**: The renderer can show "orders" by default, but provide a "Profile Toggle" to switch to "prod" and see the labels update to "orders-prod".
|
||||
|
||||
## 5. Metadata Retention
|
||||
We should never "squash" profiles during analysis. The `CodebaseMetadata` will carry the full profile matrix to the exporter.
|
||||
|
||||
## 5. SpEL (Spring Expression Language) Lite
|
||||
Full SpEL support is hard for static analysis, but we can support "Common Patterns":
|
||||
- Simple bean property access: `#{myBean.name}`
|
||||
- System properties: `#{systemProperties['user.dir']}`
|
||||
- Ternary operators: `${app.enabled ? 'active' : 'inactive'}`
|
||||
|
||||
## 6. Constant Resolution
|
||||
If an annotation uses a reference to a constant:
|
||||
```java
|
||||
@RabbitListener(queues = MyConstants.ORDER_QUEUE)
|
||||
```
|
||||
**Static Strategy**:
|
||||
1. Use JDT to resolve the `QualifiedName` (`MyConstants.ORDER_QUEUE`).
|
||||
2. Fetch the `TypeDeclaration` for `MyConstants`.
|
||||
3. Find the `VariableDeclarationFragment` for `ORDER_QUEUE` and extract its literal initializer.
|
||||
@@ -1,53 +0,0 @@
|
||||
# Advanced String and Variable Resolution Strategy
|
||||
|
||||
## 1. The Problem
|
||||
Annotations often use non-literal values for metadata:
|
||||
- **Constants**: `@PostMapping(ApiConstants.SUBMIT_PATH)`
|
||||
- **Concatenation**: `@RequestMapping(BASE + "/orders")`
|
||||
- **Inherited Variables**: Use of fields defined in base classes.
|
||||
|
||||
Simple `StringLiteral` extraction fails in these cases. We need a recursive **Expression Evaluator**.
|
||||
|
||||
## 2. Evaluation Logic
|
||||
|
||||
### Constant Resolution
|
||||
If an expression is a `SimpleName` or `QualifiedName`:
|
||||
1. Use `CodebaseContext.getTypeDeclaration()` to find the class where the variable is defined.
|
||||
2. Search for the `VariableDeclarationFragment`.
|
||||
3. If it has an initializer that is a literal or another evaluatable expression, resolve it.
|
||||
|
||||
### Concatenation (Infix Expressions)
|
||||
If an expression is an `InfixExpression` with the `+` operator:
|
||||
1. Recursively resolve the left operand.
|
||||
2. Recursively resolve the right operand.
|
||||
3. Concatenate the results if both are strings.
|
||||
|
||||
### Spring Expression Language (SpEL) in Annotations
|
||||
For values like `@Value("#{systemProperties['path.base'] + '/api'}")`:
|
||||
1. Identify the SpEL string.
|
||||
2. Use a "Lite" SpEL parser to extract keys.
|
||||
3. Match against the `profiles` map in `CodebaseMetadata`.
|
||||
|
||||
## 3. Implementation: `ValueResolver`
|
||||
We will introduce a central `ValueResolver` utility.
|
||||
|
||||
```java
|
||||
public class ValueResolver {
|
||||
public String resolveString(Expression expr, CodebaseContext context) {
|
||||
if (expr instanceof StringLiteral sl) return sl.getLiteralValue();
|
||||
if (expr instanceof InfixExpression ie) return resolveInfix(ie, context);
|
||||
if (expr instanceof Name name) return resolveVariable(name, context);
|
||||
// ... fallback to toString() or empty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Challenges
|
||||
- **Circular Dependencies**: A + B, where B = A + C. We need a "visited" set to prevent infinite recursion.
|
||||
- **Runtime-only values**: Some values cannot be resolved statically (e.g., values from a DB). In these cases, we should return the placeholder or a "Runtime Value" marker.
|
||||
- **Method Calls**: `getPath() + "/orders"`. Resolving method return values is complex; initially, we will only support simple getters returning literals.
|
||||
|
||||
## 5. Integration
|
||||
1. Update `AstUtils` or create `ValueResolver`.
|
||||
2. Refactor `SpringMvcEnricher`, `RabbitMqEnricher`, and `JmsEnricher` to use the resolver for all path/queue/destination fields.
|
||||
3. Update `integration_test_state_machine` with complex path examples.
|
||||
@@ -1,4 +1,5 @@
|
||||
task runGolden(type: JavaExec) {
|
||||
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
workingDir = rootProject.projectDir
|
||||
}
|
||||
|
||||
@@ -19,3 +19,6 @@ include ':state_machines:ultimate_ecosystem_sm'
|
||||
include ':state_machines:enterprise_order_system'
|
||||
include ':state_machines:multi_module_sample:api-module'
|
||||
include ':state_machines:multi_module_sample:core-module'
|
||||
include ':state_machines:state_machine_enterprise'
|
||||
include 'state_machine_exporter_kotlin'
|
||||
include ':state_machines:kotlin_simple_state_machine'
|
||||
|
||||
@@ -32,6 +32,9 @@ dependencies {
|
||||
// deps for parsing java AST
|
||||
implementation 'org.eclipse.jdt:org.eclipse.jdt.core:3.36.0'
|
||||
|
||||
// Kotlin plugin integration via ServiceLoader
|
||||
runtimeOnly project(':state_machine_exporter_kotlin')
|
||||
|
||||
// Jackson for JSON support
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.1'
|
||||
@@ -58,10 +61,5 @@ tasks.named('test') {
|
||||
task runGolden(type: JavaExec) {
|
||||
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
}
|
||||
|
||||
task runGoldenFixed(type: JavaExec) {
|
||||
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
|
||||
classpath = sourceSets.test.runtimeClasspath
|
||||
workingDir = rootProject.projectDir
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ import java.util.stream.Collectors;
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.matching.HeuristicEventMatchingEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine;
|
||||
|
||||
public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
|
||||
private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine();
|
||||
private final EventMatchingEngine matchingEngine = new HeuristicEventMatchingEngine();
|
||||
private final EventMatchingEngine matchingEngine = new StrictFqnMatchingEngine();
|
||||
|
||||
@Override
|
||||
public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) {
|
||||
@@ -59,7 +59,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
.targetState(smSourceRaw)
|
||||
.event(smEventRaw)
|
||||
.build();
|
||||
if (isRoutedToCorrectMachine(chain, result.getName(), context)) {
|
||||
if (isRoutedToCorrectMachine(chain, result.getName(), context)
|
||||
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
|
||||
matched.add(mt);
|
||||
}
|
||||
} else {
|
||||
@@ -70,7 +71,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
.targetState(targetRaw)
|
||||
.event(smEventRaw)
|
||||
.build();
|
||||
if (isRoutedToCorrectMachine(chain, result.getName(), context)) {
|
||||
if (isRoutedToCorrectMachine(chain, result.getName(), context)
|
||||
&& isConstraintCompatible(tp.getConstraint(), result.getName())) {
|
||||
matched.add(mt);
|
||||
}
|
||||
}
|
||||
@@ -104,20 +106,128 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName, context);
|
||||
}
|
||||
|
||||
private boolean isConstraintCompatible(String constraint, String machineName) {
|
||||
if (constraint == null || machineName == null) return true;
|
||||
|
||||
String cleanMachine = machineName.substring(machineName.lastIndexOf('.') + 1).toUpperCase();
|
||||
if (cleanMachine.endsWith("STATEMACHINECONFIGURATION")) {
|
||||
cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "STATEMACHINECONFIGURATION".length());
|
||||
}
|
||||
if (cleanMachine.endsWith("STATEMACHINE")) {
|
||||
cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "STATEMACHINE".length());
|
||||
}
|
||||
if (cleanMachine.endsWith("CONFIG")) {
|
||||
cleanMachine = cleanMachine.substring(0, cleanMachine.length() - "CONFIG".length());
|
||||
}
|
||||
|
||||
String expr = constraint;
|
||||
java.util.regex.Pattern p = java.util.regex.Pattern.compile("[\"']([a-zA-Z0-9_-]+)[\"']");
|
||||
java.util.regex.Matcher matcher = p.matcher(constraint);
|
||||
java.util.Set<String> literals = new java.util.HashSet<>();
|
||||
while (matcher.find()) {
|
||||
literals.add(matcher.group(1));
|
||||
}
|
||||
|
||||
for (String m : literals) {
|
||||
boolean isCurrent = m.equalsIgnoreCase(cleanMachine);
|
||||
String replacement = isCurrent ? "true" : "false";
|
||||
|
||||
String regex1 = "(?i)[\"']?" + m + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)";
|
||||
String regex2 = "(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?" + m + "[\"']?\\s*\\)";
|
||||
String regex3 = "(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + m + "[\"']?";
|
||||
String regex4 = "(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + m + "[\"']?";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
|
||||
private final java.util.Map<java.util.Map.Entry<String, 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) {
|
||||
if (name == null) return null;
|
||||
if (simplifyCache.containsKey(name)) return simplifyCache.get(name);
|
||||
|
||||
// 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()) {
|
||||
simplified = name;
|
||||
}
|
||||
// Simplify full identifiers to just the last part (enum name)
|
||||
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
|
||||
// For remaining full caps with underscores (like EVENT_X), keep as is or try to simplify
|
||||
simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
|
||||
|
||||
simplifyCache.put(name, simplified);
|
||||
return simplified;
|
||||
}
|
||||
|
||||
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
||||
if (smEvent == null || triggerEvent == null) return false;
|
||||
|
||||
// Use a strictly immutable pair to guarantee 100% collision-free caching regardless of string content
|
||||
java.util.Map.Entry<String, String> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(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)
|
||||
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||
@@ -135,7 +245,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
||||
// 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")
|
||||
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
|
||||
return true;
|
||||
|
||||
@@ -6,11 +6,22 @@ import java.util.List;
|
||||
|
||||
public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
|
||||
private final java.util.Map<java.util.Map.Entry<Event, TriggerPoint>, Boolean> matchesCache = new java.util.HashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
|
||||
if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
|
||||
return false;
|
||||
}
|
||||
java.util.Map.Entry<Event, TriggerPoint> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(stateMachineEvent, triggerPoint);
|
||||
if (matchesCache.containsKey(cacheKey)) return matchesCache.get(cacheKey);
|
||||
|
||||
boolean result = calculateMatches(stateMachineEvent, triggerPoint);
|
||||
matchesCache.put(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean calculateMatches(Event stateMachineEvent, TriggerPoint triggerPoint) {
|
||||
|
||||
String rawTriggerEvent = triggerPoint.getEvent();
|
||||
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
|
||||
@@ -58,7 +69,22 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
}
|
||||
|
||||
if (hasPolyMatch) return true;
|
||||
if (polyEvents.isEmpty() && isWildcard) return true;
|
||||
if (polyEvents.isEmpty() && isWildcard) {
|
||||
String eventTypeFqn = triggerPoint.getEventTypeFqn();
|
||||
if (eventTypeFqn != null && smEventRaw.contains(".")) {
|
||||
String smEventClass = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
||||
if (smEventClass.equals(eventTypeFqn)) {
|
||||
return true;
|
||||
}
|
||||
String simpleEventType = eventTypeFqn.contains(".") ? eventTypeFqn.substring(eventTypeFqn.lastIndexOf('.') + 1) : eventTypeFqn;
|
||||
String simpleSmClass = smEventClass.contains(".") ? smEventClass.substring(smEventClass.lastIndexOf('.') + 1) : smEventClass;
|
||||
if (simpleEventType.equalsIgnoreCase(simpleSmClass)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rawTriggerEvent.contains(".") && smEventRaw.contains(".")) {
|
||||
if (smEventRaw.equals(rawTriggerEvent) || smEventRaw.endsWith("." + rawTriggerEvent) || rawTriggerEvent.endsWith("." + smEventRaw)) {
|
||||
@@ -87,8 +113,23 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
return isGuardedContains(smEvent, triggerEvent);
|
||||
}
|
||||
|
||||
private final java.util.Map<String, String> simplifyCache = new java.util.HashMap<>();
|
||||
private final java.util.Map<java.util.Map.Entry<String, 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) {
|
||||
if (smEvent == null || triggerEvent == null) return false;
|
||||
|
||||
java.util.Map.Entry<String, String> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(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)
|
||||
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||
@@ -116,18 +157,36 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
}
|
||||
|
||||
private boolean isWildcardVariable(String eventStr) {
|
||||
return eventStr.equals("event") || eventStr.equals("e") ||
|
||||
if (eventStr == null) return false;
|
||||
|
||||
if (eventStr.equals("event") || eventStr.equals("e") ||
|
||||
eventStr.equals("msg") || eventStr.equals("message") ||
|
||||
eventStr.equals("payload");
|
||||
eventStr.equals("payload")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String simplify(String name) {
|
||||
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()) {
|
||||
simplified = name;
|
||||
}
|
||||
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
|
||||
simplified = FQN_PATTERN.matcher(simplified).replaceAll("$1");
|
||||
|
||||
simplifyCache.put(name, simplified);
|
||||
return simplified;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.model.Event;
|
||||
import java.util.List;
|
||||
|
||||
public class StrictFqnMatchingEngine implements EventMatchingEngine {
|
||||
|
||||
@Override
|
||||
public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
|
||||
if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (triggerPoint.isExternal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
|
||||
String rawTriggerEvent = triggerPoint.getEvent();
|
||||
|
||||
if (rawTriggerEvent != null && rawTriggerEvent.startsWith("<SYMBOLIC: ") && rawTriggerEvent.endsWith(".*>")) {
|
||||
String targetType = rawTriggerEvent.substring(11, rawTriggerEvent.length() - 3);
|
||||
if (smEventRaw.startsWith(targetType + ".")) {
|
||||
return true;
|
||||
}
|
||||
if (smEventRaw.contains(".")) {
|
||||
String smType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
||||
if (smType.endsWith("." + targetType) || smType.equals(targetType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
|
||||
|
||||
for (String pe : polyEvents) {
|
||||
if (matchEventNames(pe, smEventRaw, triggerPoint.getEventTypeFqn())) {
|
||||
return true;
|
||||
}
|
||||
if (pe.startsWith("<SYMBOLIC: ") && pe.endsWith(".*>")) {
|
||||
String targetType = pe.substring(11, pe.length() - 3);
|
||||
if (smEventRaw.startsWith(targetType + ".")) {
|
||||
return true;
|
||||
}
|
||||
if (smEventRaw.contains(".")) {
|
||||
String smType = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
||||
if (smType.endsWith("." + targetType) || smType.equals(targetType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rawTriggerEvent != null && rawTriggerEvent.contains(".valueOf(")) {
|
||||
String enumClass = rawTriggerEvent.substring(0, rawTriggerEvent.indexOf(".valueOf("));
|
||||
if (enumClass.contains(" ")) {
|
||||
enumClass = enumClass.substring(enumClass.lastIndexOf(" ") + 1);
|
||||
}
|
||||
String smClass = smEventRaw.contains(".") ? smEventRaw.substring(0, smEventRaw.lastIndexOf('.')) : smEventRaw;
|
||||
if (enumClass.equals(smClass) || enumClass.endsWith("." + smClass) || smClass.endsWith("." + enumClass)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (polyEvents.isEmpty() && isDynamicVariable(rawTriggerEvent)) {
|
||||
if (triggerPoint.getEventTypeFqn() != null) {
|
||||
if (smEventRaw.startsWith(triggerPoint.getEventTypeFqn() + ".")) {
|
||||
return true;
|
||||
}
|
||||
if (isStringTypeOrPrimitive(triggerPoint.getEventTypeFqn()) && !smEventRaw.contains(".")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return matchEventNames(rawTriggerEvent, smEventRaw, triggerPoint.getEventTypeFqn());
|
||||
}
|
||||
|
||||
private boolean matchEventNames(String triggerEvent, String smEvent, String eventTypeFqn) {
|
||||
if (triggerEvent == null || smEvent == null) return false;
|
||||
if (triggerEvent.equals(smEvent)) return true;
|
||||
|
||||
String tConst = triggerEvent.contains(".") ? triggerEvent.substring(triggerEvent.lastIndexOf('.') + 1) : triggerEvent;
|
||||
String smConst = smEvent.contains(".") ? smEvent.substring(smEvent.lastIndexOf('.') + 1) : smEvent;
|
||||
|
||||
if (!tConst.equals(smConst)) return false;
|
||||
|
||||
// Prevent matching string/primitive triggers to enum transitions
|
||||
if (isStringTypeOrPrimitive(eventTypeFqn) && smEvent.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent matching enum triggers to string transitions
|
||||
if (!smEvent.contains(".") && triggerEvent.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventTypeFqn != null && eventTypeFqn.contains(".") && smEvent.contains(".")) {
|
||||
String fullTriggerFqn = eventTypeFqn + "." + tConst;
|
||||
return fullTriggerFqn.equals(smEvent);
|
||||
}
|
||||
|
||||
if (triggerEvent.contains(".") && smEvent.contains(".")) {
|
||||
String tClass = triggerEvent.substring(0, triggerEvent.lastIndexOf('.'));
|
||||
String smClass = smEvent.substring(0, smEvent.lastIndexOf('.'));
|
||||
String tClassSimple = tClass.contains(".") ? tClass.substring(tClass.lastIndexOf('.') + 1) : tClass;
|
||||
String smClassSimple = smClass.contains(".") ? smClass.substring(smClass.lastIndexOf('.') + 1) : smClass;
|
||||
return tClassSimple.equals(smClassSimple);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isStringTypeOrPrimitive(String typeFqn) {
|
||||
if (typeFqn == null) return false;
|
||||
return typeFqn.equals("String") || typeFqn.equals("java.lang.String") ||
|
||||
typeFqn.equals("int") || typeFqn.equals("long") || typeFqn.equals("char");
|
||||
}
|
||||
|
||||
private boolean isDynamicVariable(String eventStr) {
|
||||
if (eventStr == null || eventStr.isEmpty()) return false;
|
||||
|
||||
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventStr.endsWith("()")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventStr.contains(".")) {
|
||||
String firstPart = eventStr.substring(0, eventStr.indexOf('.'));
|
||||
if (!firstPart.isEmpty() && Character.isLowerCase(firstPart.charAt(0))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Character.isLowerCase(eventStr.charAt(0))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -43,10 +43,12 @@ public class SymbolicPathValueEstimator implements PathValueEstimator {
|
||||
|
||||
click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver resolver = new click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver();
|
||||
|
||||
if (caller != null) {
|
||||
String callerVal = resolver.resolve(caller, context);
|
||||
if (callerVal != null && !callerVal.isEmpty()) {
|
||||
addValue(collectedConstants, callerVal);
|
||||
}
|
||||
}
|
||||
|
||||
String argVal = resolver.resolve(arg, context);
|
||||
if (argVal != null && !argVal.isEmpty()) {
|
||||
|
||||
@@ -364,6 +364,7 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||
if (type1 == null || type2 == null) return false;
|
||||
type1 = eraseGenerics(type1);
|
||||
type2 = eraseGenerics(type2);
|
||||
if (type1.length() == 1 || type2.length() == 1) return false;
|
||||
if (type1.equals("java.lang.String") || type1.equals("String") || type1.equals("java.lang.Object") || type1.equals("Object")) return false;
|
||||
if (type2.equals("java.lang.String") || type2.equals("String") || type2.equals("java.lang.Object") || type2.equals("Object")) return false;
|
||||
return !type1.equals(type2);
|
||||
|
||||
@@ -13,8 +13,13 @@ public class CallEdge {
|
||||
private String targetMethod;
|
||||
private List<String> arguments;
|
||||
private String receiver;
|
||||
private String constraint;
|
||||
|
||||
public CallEdge(String targetMethod, List<String> arguments) {
|
||||
this(targetMethod, arguments, null);
|
||||
this(targetMethod, arguments, null, null);
|
||||
}
|
||||
|
||||
public CallEdge(String targetMethod, List<String> arguments, String receiver) {
|
||||
this(targetMethod, arguments, receiver, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,106 @@ public class TriggerPoint {
|
||||
@com.fasterxml.jackson.annotation.JsonIgnore
|
||||
private final String eventTypeFqn; // Type of Event (e.g. OrderEvents FQN)
|
||||
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
|
||||
private final boolean external;
|
||||
private final String constraint;
|
||||
private final boolean ambiguous;
|
||||
|
||||
public TriggerPoint(
|
||||
String event,
|
||||
String className,
|
||||
String methodName,
|
||||
String sourceFile,
|
||||
String sourceModule,
|
||||
String stateMachineId,
|
||||
String sourceState,
|
||||
int lineNumber,
|
||||
String stateTypeFqn,
|
||||
String eventTypeFqn,
|
||||
java.util.List<String> polymorphicEvents,
|
||||
boolean external,
|
||||
String constraint,
|
||||
boolean ambiguous) {
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.sourceFile = sourceFile;
|
||||
this.sourceModule = sourceModule;
|
||||
this.stateMachineId = stateMachineId;
|
||||
this.sourceState = sourceState;
|
||||
this.lineNumber = lineNumber;
|
||||
this.stateTypeFqn = stateTypeFqn;
|
||||
this.eventTypeFqn = eventTypeFqn;
|
||||
this.event = qualifyEvent(event, eventTypeFqn);
|
||||
if (polymorphicEvents != null) {
|
||||
this.polymorphicEvents = polymorphicEvents.stream()
|
||||
.map(pe -> qualifyEvent(pe, eventTypeFqn))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
this.polymorphicEvents = null;
|
||||
}
|
||||
this.external = external;
|
||||
this.constraint = constraint;
|
||||
this.ambiguous = ambiguous;
|
||||
}
|
||||
|
||||
private static String qualifyEvent(String e, String eventTypeFqn) {
|
||||
if (e == null || eventTypeFqn == null || e.isEmpty()) {
|
||||
return e;
|
||||
}
|
||||
if (e.startsWith("<SYMBOLIC: ")) {
|
||||
return e;
|
||||
}
|
||||
if (isWildcardVariable(e)) {
|
||||
return e;
|
||||
}
|
||||
if (Character.isLowerCase(e.charAt(0))) {
|
||||
return e;
|
||||
}
|
||||
if (!isEnumConstantCandidate(e)) {
|
||||
return e;
|
||||
}
|
||||
|
||||
String simpleEventType = eventTypeFqn.contains(".") ? eventTypeFqn.substring(eventTypeFqn.lastIndexOf('.') + 1) : eventTypeFqn;
|
||||
|
||||
if (e.contains(".")) {
|
||||
String qualifier = e.substring(0, e.lastIndexOf('.'));
|
||||
String lastSegment = e.substring(e.lastIndexOf('.') + 1);
|
||||
String simpleQualifier = qualifier.contains(".") ? qualifier.substring(qualifier.lastIndexOf('.') + 1) : qualifier;
|
||||
|
||||
if (simpleQualifier.equals(simpleEventType)) {
|
||||
return simpleEventType + "." + lastSegment;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
if (eventTypeFqn.startsWith("java.lang.") || eventTypeFqn.equals("String") || eventTypeFqn.equals("int") || eventTypeFqn.equals("long") || eventTypeFqn.equals("char")) {
|
||||
return e;
|
||||
}
|
||||
return simpleEventType + "." + e;
|
||||
}
|
||||
|
||||
private static boolean isEnumConstantCandidate(String eventStr) {
|
||||
if (eventStr == null) return false;
|
||||
if (eventStr.startsWith("<SYMBOLIC: ")) {
|
||||
return false;
|
||||
}
|
||||
if (eventStr.contains(" ") || eventStr.contains("(") || eventStr.contains(")") || eventStr.contains("?")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isWildcardVariable(String eventStr) {
|
||||
if (eventStr == null) return false;
|
||||
if (eventStr.equals("event") || eventStr.equals("e") ||
|
||||
eventStr.equals("msg") || eventStr.equals("message") ||
|
||||
eventStr.equals("payload")) {
|
||||
return true;
|
||||
}
|
||||
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
||||
return true;
|
||||
}
|
||||
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import org.eclipse.jdt.core.dom.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class ConstantResolver {
|
||||
@@ -93,28 +96,43 @@ public class ConstantResolver {
|
||||
|
||||
// Fallback for unresolved AST nodes
|
||||
if (mi.getExpression() instanceof QualifiedName qn) {
|
||||
System.out.println("RETURNING qn name: " + qn.getName().getIdentifier()); return qn.getName().getIdentifier();
|
||||
return qn.getName().getIdentifier();
|
||||
} else if (mi.getExpression() instanceof SimpleName sn) {
|
||||
System.out.println("RETURNING sn name: " + sn.getIdentifier()); return sn.getIdentifier();
|
||||
return sn.getIdentifier();
|
||||
}
|
||||
}
|
||||
|
||||
if ("valueOf".equals(mi.getName().getIdentifier()) && mi.getExpression() != null) {
|
||||
java.util.List<String> enumValues = context.getEnumValues(mi.getExpression().toString());
|
||||
if (enumValues != null && !enumValues.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", enumValues);
|
||||
if ("valueOf".equals(mi.getName().getIdentifier())) {
|
||||
String enumFqn = null;
|
||||
Expression arg = null;
|
||||
if (mi.arguments().size() == 2) {
|
||||
enumFqn = resolveEnumFqn((Expression) mi.arguments().get(0), context);
|
||||
arg = (Expression) mi.arguments().get(1);
|
||||
} else if (mi.arguments().size() == 1 && mi.getExpression() != null) {
|
||||
enumFqn = resolveEnumFqn(mi.getExpression(), context);
|
||||
arg = (Expression) mi.arguments().get(0);
|
||||
}
|
||||
|
||||
if (enumFqn != null) {
|
||||
if (arg != null) {
|
||||
String resolvedArg = resolveInternal(arg, context, visited);
|
||||
if (resolvedArg != null && resolvedArg.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
return enumFqn + "." + resolvedArg;
|
||||
}
|
||||
}
|
||||
return "<SYMBOLIC: " + enumFqn + ".*>";
|
||||
}
|
||||
}
|
||||
|
||||
IMethodBinding mb = mi.resolveMethodBinding();
|
||||
if (mb != null) {
|
||||
ITypeBinding returnType = mb.getReturnType();
|
||||
if (returnType != null) {
|
||||
java.util.List<String> values = context.getEnumValues(returnType.getQualifiedName());
|
||||
if (values != null && !values.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", values);
|
||||
if (!java.lang.reflect.Modifier.isStatic(mb.getModifiers())) {
|
||||
Expression receiver = mi.getExpression();
|
||||
if (!(receiver instanceof ClassInstanceCreation)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
TypeDeclaration td = null;
|
||||
if (mi.getExpression() != null) {
|
||||
@@ -147,8 +165,7 @@ public class ConstantResolver {
|
||||
if (md != null) {
|
||||
String evaluated = evaluateMethodOutput(mi, md, context, visited);
|
||||
if (evaluated != null) return evaluated;
|
||||
|
||||
if (md.getReturnType2() != null) {
|
||||
if (mi.getName().getIdentifier().startsWith("get") && md.getReturnType2() != null) {
|
||||
java.util.List<String> values = context.getEnumValues(md.getReturnType2().toString());
|
||||
if (values != null && !values.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", values);
|
||||
@@ -158,7 +175,6 @@ public class ConstantResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -223,9 +239,8 @@ public class ConstantResolver {
|
||||
}
|
||||
|
||||
try {
|
||||
java.util.Map<String, String> localVars = new java.util.HashMap<>(prebuiltLocals);
|
||||
java.util.Set<String> declaredLocals = new java.util.HashSet<>(prebuiltLocals.keySet());
|
||||
return evaluateBody(md.getBody(), localVars, declaredLocals, context, visited);
|
||||
return evaluateBody(md.getBody(), prebuiltLocals, declaredLocals, context, visited);
|
||||
} finally {
|
||||
visited.remove(methodFqn);
|
||||
}
|
||||
@@ -251,15 +266,61 @@ public class ConstantResolver {
|
||||
String varName = fragment.getName().getIdentifier();
|
||||
declaredLocals.add(varName);
|
||||
if (fragment.getInitializer() != null) {
|
||||
String rhsVal = resolveExpressionWithParams(fragment.getInitializer(), localVars);
|
||||
String rhsVal = resolveExpressionWithParams(fragment.getInitializer(), localVars, context);
|
||||
if (rhsVal == null) rhsVal = resolveInternal(fragment.getInitializer(), context, visited);
|
||||
if (rhsVal != null) localVars.put(varName, rhsVal);
|
||||
if (rhsVal != null) {
|
||||
localVars.put(varName, rhsVal);
|
||||
} else {
|
||||
java.util.Map<String, String> bFields = extractBuilderFields(fragment.getInitializer(), localVars, context, visited);
|
||||
if (bFields != null) {
|
||||
for (java.util.Map.Entry<String, String> e : bFields.entrySet()) {
|
||||
localVars.put(varName + "." + e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(vds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(org.eclipse.jdt.core.dom.MethodInvocation node) {
|
||||
MethodDeclaration sideEffectMd = findInvokedMethod(node, context);
|
||||
if (sideEffectMd != null && sideEffectMd.getBody() != null) {
|
||||
TypeDeclaration currentTd = findEnclosingType(sideEffectMd);
|
||||
if (sideEffectMd != null && sideEffectMd.getBody() != null) {
|
||||
java.util.Map<String, String> inlineLocals = new java.util.HashMap<>();
|
||||
for (java.util.Map.Entry<String, String> entry : localVars.entrySet()) {
|
||||
if (entry.getKey().startsWith("this.")) {
|
||||
inlineLocals.put(entry.getKey(), entry.getValue());
|
||||
} else if (context.hasField(currentTd, entry.getKey())) {
|
||||
inlineLocals.put(entry.getKey(), entry.getValue());
|
||||
inlineLocals.put("this." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < node.arguments().size() && i < sideEffectMd.parameters().size(); i++) {
|
||||
Expression arg = (Expression) node.arguments().get(i);
|
||||
String val = resolveExpressionWithParams(arg, localVars, context);
|
||||
if (val == null) val = resolveInternal(arg, context, visited);
|
||||
if (val != null) {
|
||||
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) sideEffectMd.parameters().get(i);
|
||||
inlineLocals.put(param.getName().getIdentifier(), val);
|
||||
}
|
||||
}
|
||||
evaluateMethodBodyWithLocals(sideEffectMd, inlineLocals, context, visited);
|
||||
for (java.util.Map.Entry<String, String> entry : inlineLocals.entrySet()) {
|
||||
if (entry.getKey().startsWith("this.")) {
|
||||
localVars.put(entry.getKey(), entry.getValue());
|
||||
String bareName = entry.getKey().substring(5);
|
||||
if (!declaredLocals.contains(bareName)) localVars.put(bareName, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Assignment assignment) {
|
||||
Expression lhs = assignment.getLeftHandSide();
|
||||
@@ -269,9 +330,19 @@ public class ConstantResolver {
|
||||
} else if (lhs instanceof FieldAccess fa) {
|
||||
varName = "this." + fa.getName().getIdentifier();
|
||||
}
|
||||
String rhsVal = resolveExpressionWithParams(assignment.getRightHandSide(), localVars);
|
||||
if (varName != null) {
|
||||
String rhsVal = resolveExpressionWithParams(assignment.getRightHandSide(), localVars, context);
|
||||
if (rhsVal == null) rhsVal = resolveInternal(assignment.getRightHandSide(), context, visited);
|
||||
if (varName != null && rhsVal != null) {
|
||||
if (rhsVal != null) {
|
||||
localVars.put(varName, rhsVal);
|
||||
} else {
|
||||
java.util.Map<String, String> bFields = extractBuilderFields(assignment.getRightHandSide(), localVars, context, visited);
|
||||
if (bFields != null) {
|
||||
for (java.util.Map.Entry<String, String> e : bFields.entrySet()) {
|
||||
localVars.put(varName + "." + e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (varName.startsWith("this.")) {
|
||||
localVars.put(varName, rhsVal);
|
||||
String bareName = varName.substring(5);
|
||||
@@ -300,7 +371,7 @@ public class ConstantResolver {
|
||||
String result = evaluateSwitchExpression(se, localVars, context, visited);
|
||||
if (result != null) finalResult[0] = result;
|
||||
} else if (rs.getExpression() != null) {
|
||||
String result = resolveExpressionWithParams(rs.getExpression(), localVars);
|
||||
String result = resolveExpressionWithParams(rs.getExpression(), localVars, context);
|
||||
if (result == null) result = resolveInternal(rs.getExpression(), context, visited);
|
||||
if (result != null) finalResult[0] = result;
|
||||
}
|
||||
@@ -313,8 +384,44 @@ public class ConstantResolver {
|
||||
}
|
||||
|
||||
private String evaluateSwitchStatement(org.eclipse.jdt.core.dom.SwitchStatement ss, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
|
||||
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues);
|
||||
if (switchVar == null) return null;
|
||||
String switchVar = resolveExpressionWithParams(ss.getExpression(), paramValues, context);
|
||||
if (switchVar == null) {
|
||||
Set<String> values = new LinkedHashSet<>();
|
||||
for (Object stmtObj : ss.statements()) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) {
|
||||
String val = resolveInternal(rs.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
|
||||
String val = resolveInternal(ys.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es && es.getExpression() != null) {
|
||||
String val = resolveInternal(es.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String switchType = null;
|
||||
if (switchVar.contains(".")) {
|
||||
@@ -329,7 +436,7 @@ public class ConstantResolver {
|
||||
matchingCase = true;
|
||||
} else {
|
||||
for (Object exprObj : sc.expressions()) {
|
||||
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues);
|
||||
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues, context);
|
||||
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
|
||||
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
||||
caseVal = caseSn.getIdentifier();
|
||||
@@ -364,8 +471,44 @@ public class ConstantResolver {
|
||||
}
|
||||
|
||||
private String evaluateSwitchExpression(org.eclipse.jdt.core.dom.SwitchExpression se, java.util.Map<String, String> paramValues, CodebaseContext context, Set<String> visited) {
|
||||
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues);
|
||||
if (switchVar == null) return null;
|
||||
String switchVar = resolveExpressionWithParams(se.getExpression(), paramValues, context);
|
||||
if (switchVar == null) {
|
||||
Set<String> values = new LinkedHashSet<>();
|
||||
for (Object stmtObj : se.statements()) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
|
||||
String val = resolveInternal(ys.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es && es.getExpression() != null) {
|
||||
String val = resolveInternal(es.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
} else if (stmtObj instanceof org.eclipse.jdt.core.dom.ReturnStatement rs && rs.getExpression() != null) {
|
||||
String val = resolveInternal(rs.getExpression(), context, visited);
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
values.addAll(Arrays.asList(val.substring(9).split(",")));
|
||||
} else {
|
||||
values.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
return "ENUM_SET:" + String.join(",", values);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String switchType = null;
|
||||
if (switchVar.contains(".")) {
|
||||
@@ -380,7 +523,7 @@ public class ConstantResolver {
|
||||
matchingCase = true;
|
||||
} else {
|
||||
for (Object exprObj : sc.expressions()) {
|
||||
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues);
|
||||
String caseVal = resolveExpressionWithParams((Expression) exprObj, paramValues, context);
|
||||
// Bare SimpleName case label (e.g. "case PAY"): use identifier as fallback
|
||||
if (caseVal == null && exprObj instanceof org.eclipse.jdt.core.dom.SimpleName caseSn) {
|
||||
caseVal = caseSn.getIdentifier();
|
||||
@@ -414,7 +557,7 @@ public class ConstantResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveExpressionWithParams(Expression expr, java.util.Map<String, String> paramValues) {
|
||||
private String resolveExpressionWithParams(Expression expr, java.util.Map<String, String> paramValues, CodebaseContext context) {
|
||||
if (expr instanceof SimpleName sn) {
|
||||
String name = sn.getIdentifier();
|
||||
if (paramValues.containsKey(name)) return paramValues.get(name);
|
||||
@@ -427,10 +570,107 @@ public class ConstantResolver {
|
||||
return null;
|
||||
} else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) {
|
||||
return sl.getLiteralValue();
|
||||
} else if (expr instanceof MethodInvocation mi) {
|
||||
if ("valueOf".equals(mi.getName().getIdentifier())) {
|
||||
Expression arg = null;
|
||||
if (mi.arguments().size() == 1) {
|
||||
arg = (Expression) mi.arguments().get(0);
|
||||
} else if (mi.arguments().size() == 2) {
|
||||
arg = (Expression) mi.arguments().get(1);
|
||||
}
|
||||
if (arg != null) {
|
||||
String resolvedArg = resolveExpressionWithParams(arg, paramValues, context);
|
||||
if (resolvedArg != null && resolvedArg.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
String enumFqn = null;
|
||||
if (mi.arguments().size() == 2) {
|
||||
enumFqn = resolveEnumFqn((Expression) mi.arguments().get(0), context);
|
||||
} else if (mi.arguments().size() == 1 && mi.getExpression() != null) {
|
||||
enumFqn = resolveEnumFqn(mi.getExpression(), context);
|
||||
}
|
||||
if (enumFqn != null) {
|
||||
return enumFqn + "." + resolvedArg;
|
||||
} else {
|
||||
// If we can't resolve the exact enum type here, we can just return the bare constant
|
||||
return resolvedArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mi.arguments().isEmpty() && mi.getExpression() instanceof SimpleName sn) {
|
||||
String objName = sn.getIdentifier();
|
||||
String methodName = mi.getName().getIdentifier();
|
||||
String propName = null;
|
||||
if (methodName.startsWith("get") && methodName.length() > 3) {
|
||||
propName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
|
||||
} else {
|
||||
propName = methodName;
|
||||
}
|
||||
String val = paramValues.get(objName + "." + propName);
|
||||
if (val == null && !propName.equals(methodName)) {
|
||||
val = paramValues.get(objName + "." + methodName);
|
||||
}
|
||||
if (val != null) return val;
|
||||
}
|
||||
|
||||
MethodDeclaration sideEffectMd = findInvokedMethod(mi, context);
|
||||
if (sideEffectMd != null && sideEffectMd.getBody() != null) {
|
||||
TypeDeclaration currentTd = findEnclosingType(sideEffectMd);
|
||||
java.util.Map<String, String> inlineLocals = new java.util.HashMap<>();
|
||||
for (java.util.Map.Entry<String, String> entry : paramValues.entrySet()) {
|
||||
if (entry.getKey().startsWith("this.")) {
|
||||
inlineLocals.put(entry.getKey(), entry.getValue());
|
||||
} else if (context.hasField(currentTd, entry.getKey())) {
|
||||
inlineLocals.put(entry.getKey(), entry.getValue());
|
||||
inlineLocals.put("this." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < mi.arguments().size() && i < sideEffectMd.parameters().size(); i++) {
|
||||
Expression arg = (Expression) mi.arguments().get(i);
|
||||
String val = resolveExpressionWithParams(arg, paramValues, context);
|
||||
if (val == null) val = resolveInternal(arg, context, new java.util.HashSet<>());
|
||||
if (val != null) {
|
||||
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) sideEffectMd.parameters().get(i);
|
||||
inlineLocals.put(param.getName().getIdentifier(), val);
|
||||
}
|
||||
}
|
||||
String ret = evaluateMethodBodyWithLocals(sideEffectMd, inlineLocals, context, new java.util.HashSet<>());
|
||||
for (java.util.Map.Entry<String, String> entry : inlineLocals.entrySet()) {
|
||||
if (entry.getKey().startsWith("this.")) {
|
||||
paramValues.put(entry.getKey(), entry.getValue());
|
||||
String bareName = entry.getKey().substring(5);
|
||||
paramValues.put(bareName, entry.getValue());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private java.util.Map<String, String> extractBuilderFields(Expression expr, java.util.Map<String, String> localVars, CodebaseContext context, Set<String> visited) {
|
||||
if (!(expr instanceof MethodInvocation mi)) return null;
|
||||
java.util.Map<String, String> fields = new java.util.HashMap<>();
|
||||
MethodInvocation current = mi;
|
||||
while (current != null) {
|
||||
String methodName = current.getName().getIdentifier();
|
||||
if (!"build".equals(methodName) && !"builder".equals(methodName) && !"toBuilder".equals(methodName)) {
|
||||
if (current.arguments().size() == 1) {
|
||||
Expression arg = (Expression) current.arguments().get(0);
|
||||
String val = resolveExpressionWithParams(arg, localVars, context);
|
||||
if (val == null) val = resolveInternal(arg, context, visited);
|
||||
if (val != null) fields.put(methodName, val);
|
||||
}
|
||||
}
|
||||
if (current.getExpression() instanceof MethodInvocation next) {
|
||||
current = next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fields.isEmpty() ? null : fields;
|
||||
}
|
||||
|
||||
private String resolveInfix(InfixExpression expr, CodebaseContext context, Set<String> visited) {
|
||||
if (expr.getOperator() != InfixExpression.Operator.PLUS) return null;
|
||||
|
||||
@@ -558,12 +798,51 @@ public class ConstantResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check superclass
|
||||
if (td.getSuperclassType() != null) {
|
||||
String superclassName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(td.getSuperclassType());
|
||||
String resolvedSuperclass = resolveTypeFqn(superclassName, context, td);
|
||||
if (resolvedSuperclass != null) {
|
||||
TypeDeclaration superTd = context.getTypeDeclaration(resolvedSuperclass);
|
||||
if (superTd != null) {
|
||||
String result = resolveFieldInType(superTd, fieldName, resolvedSuperclass, context, visited);
|
||||
if (result != null) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check superinterfaces
|
||||
for (Object intfObj : td.superInterfaceTypes()) {
|
||||
Type intfType = (Type) intfObj;
|
||||
String intfName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(intfType);
|
||||
String resolvedIntf = resolveTypeFqn(intfName, context, td);
|
||||
if (resolvedIntf != null) {
|
||||
TypeDeclaration intfTd = context.getTypeDeclaration(resolvedIntf);
|
||||
if (intfTd != null) {
|
||||
String result = resolveFieldInType(intfTd, fieldName, resolvedIntf, context, visited);
|
||||
if (result != null) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
visited.remove(fieldId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveTypeFqn(String simpleName, CodebaseContext context, TypeDeclaration td) {
|
||||
CompilationUnit cu = (CompilationUnit) td.getRoot();
|
||||
for (Object impObj : cu.imports()) {
|
||||
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||
String name = imp.getName().getFullyQualifiedName();
|
||||
if (name.endsWith("." + simpleName)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return cu.getPackage().getName().getFullyQualifiedName() + "." + simpleName;
|
||||
}
|
||||
|
||||
private String findValueFromAnnotation(FieldDeclaration fd) {
|
||||
for (Object mod : fd.modifiers()) {
|
||||
if (mod instanceof Annotation ann) {
|
||||
@@ -597,11 +876,29 @@ public class ConstantResolver {
|
||||
}
|
||||
|
||||
private TypeDeclaration findEnclosingType(ASTNode node) {
|
||||
ASTNode parent = node.getParent();
|
||||
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
||||
parent = parent.getParent();
|
||||
ASTNode current = node;
|
||||
while (current != null && !(current instanceof TypeDeclaration)) {
|
||||
current = current.getParent();
|
||||
}
|
||||
return (TypeDeclaration) parent;
|
||||
return (TypeDeclaration) current;
|
||||
}
|
||||
|
||||
private MethodDeclaration findInvokedMethod(org.eclipse.jdt.core.dom.MethodInvocation mi, CodebaseContext context) {
|
||||
org.eclipse.jdt.core.dom.IMethodBinding methodBinding = mi.resolveMethodBinding();
|
||||
if (methodBinding != null) {
|
||||
org.eclipse.jdt.core.dom.ITypeBinding declaringClass = methodBinding.getDeclaringClass();
|
||||
if (declaringClass != null) {
|
||||
TypeDeclaration targetTd = context.getTypeDeclaration(declaringClass.getErasure().getQualifiedName());
|
||||
if (targetTd != null) {
|
||||
return context.findMethodDeclaration(targetTd, mi.getName().getIdentifier(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDeclaration currentTd = findEnclosingType(mi);
|
||||
if (currentTd != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) {
|
||||
return context.findMethodDeclaration(currentTd, mi.getName().getIdentifier(), true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||
@@ -648,4 +945,81 @@ public class ConstantResolver {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveEnumFqn(Expression expr, CodebaseContext context) {
|
||||
if (expr == null) return null;
|
||||
CompilationUnit contextCu = (expr.getRoot() instanceof CompilationUnit) ? (CompilationUnit) expr.getRoot() : null;
|
||||
|
||||
if (expr instanceof TypeLiteral tl) {
|
||||
ITypeBinding binding = tl.getType().resolveBinding();
|
||||
if (binding != null) {
|
||||
return binding.getQualifiedName();
|
||||
}
|
||||
String typeName = tl.getType().toString();
|
||||
if (typeName.endsWith(".class")) {
|
||||
typeName = typeName.substring(0, typeName.length() - 6);
|
||||
}
|
||||
String resolved = resolveTypeNameToFqn(typeName, contextCu, context);
|
||||
if (resolved != null) return resolved;
|
||||
return typeName;
|
||||
}
|
||||
|
||||
ITypeBinding binding = expr.resolveTypeBinding();
|
||||
if (binding != null) {
|
||||
if (binding.isClass() && "java.lang.Class".equals(binding.getErasure().getQualifiedName())) {
|
||||
ITypeBinding[] typeArgs = binding.getTypeArguments();
|
||||
if (typeArgs.length > 0) {
|
||||
return typeArgs[0].getQualifiedName();
|
||||
}
|
||||
}
|
||||
return binding.getQualifiedName();
|
||||
}
|
||||
|
||||
String exprStr = expr.toString();
|
||||
if (exprStr.endsWith(".class")) {
|
||||
exprStr = exprStr.substring(0, exprStr.length() - 6);
|
||||
}
|
||||
String resolved = resolveTypeNameToFqn(exprStr, contextCu, context);
|
||||
if (resolved != null) return resolved;
|
||||
return exprStr;
|
||||
}
|
||||
|
||||
private String resolveTypeNameToFqn(String name, CompilationUnit contextCu, CodebaseContext context) {
|
||||
if (name == null || name.isEmpty()) return null;
|
||||
|
||||
if (context.getTypeDeclaration(name) != null || context.getEnumValuesMap().containsKey(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (contextCu != null) {
|
||||
for (Object typeObj : contextCu.types()) {
|
||||
if (typeObj instanceof AbstractTypeDeclaration atd) {
|
||||
if (atd.getName().getIdentifier().equals(name)) {
|
||||
String pkg = contextCu.getPackage() != null ? contextCu.getPackage().getName().getFullyQualifiedName() : "";
|
||||
return pkg.isEmpty() ? name : pkg + "." + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Object impObj : contextCu.imports()) {
|
||||
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||
String impName = imp.getName().getFullyQualifiedName();
|
||||
if (!imp.isStatic() && !imp.isOnDemand()) {
|
||||
if (impName.endsWith("." + name)) {
|
||||
return impName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contextCu.getPackage() != null) {
|
||||
String pkg = contextCu.getPackage().getName().getFullyQualifiedName();
|
||||
String pkgFqn = pkg + "." + name;
|
||||
if (context.getEnumValuesMap().containsKey(pkgFqn) || context.getTypeDeclaration(pkgFqn) != null) {
|
||||
return pkgFqn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,28 @@ public class SiblingDependencyResolver {
|
||||
private static final Pattern MAVEN_DEPENDENCY = Pattern.compile("<dependency>\\s*<groupId>(.*?)</groupId>\\s*<artifactId>(.*?)</artifactId>\\s*<version>(.*?)</version>", Pattern.DOTALL);
|
||||
|
||||
public Set<Path> resolveSiblings(Path projectDir) {
|
||||
Set<Path> siblings = new HashSet<>();
|
||||
Set<Path> allSiblings = new HashSet<>();
|
||||
Queue<Path> queue = new LinkedList<>();
|
||||
queue.add(projectDir);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Path current = queue.poll();
|
||||
Set<Path> currentSiblings = new HashSet<>();
|
||||
try {
|
||||
siblings.addAll(resolveGradleSiblings(projectDir));
|
||||
siblings.addAll(resolveMavenSiblings(projectDir));
|
||||
currentSiblings.addAll(resolveGradleSiblings(current));
|
||||
currentSiblings.addAll(resolveMavenSiblings(current));
|
||||
} catch (Exception e) {
|
||||
log.warn("Error resolving sibling dependencies for {}", projectDir, e);
|
||||
log.warn("Error resolving sibling dependencies for {}", current, e);
|
||||
}
|
||||
return siblings;
|
||||
|
||||
for (Path sibling : currentSiblings) {
|
||||
if (!allSiblings.contains(sibling) && !sibling.equals(projectDir)) {
|
||||
allSiblings.add(sibling);
|
||||
queue.add(sibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allSiblings;
|
||||
}
|
||||
|
||||
private Set<Path> resolveGradleSiblings(Path projectDir) throws IOException {
|
||||
@@ -52,6 +66,23 @@ public class SiblingDependencyResolver {
|
||||
for (String gradlePath : gradlePaths) {
|
||||
String relativePath = gradlePath.replace(':', '/');
|
||||
Path resolvedSibling = rootDir.resolve(relativePath).normalize();
|
||||
|
||||
// Check for explicit projectDir mapping in settings.gradle or settings.gradle.kts
|
||||
Path settingsFile = rootDir.resolve("settings.gradle");
|
||||
if (!Files.exists(settingsFile)) {
|
||||
settingsFile = rootDir.resolve("settings.gradle.kts");
|
||||
}
|
||||
if (Files.exists(settingsFile)) {
|
||||
String settingsContent = Files.readString(settingsFile);
|
||||
String regex = "project\\(['\"]:" + gradlePath + "['\"]\\)\\.projectDir\\s*=\\s*(?:file\\(['\"](.*?)['\"]\\)|new File\\(.*?,\\s*['\"](.*?)['\"]\\))";
|
||||
Matcher settingsMatcher = Pattern.compile(regex).matcher(settingsContent);
|
||||
if (settingsMatcher.find()) {
|
||||
String customPath = settingsMatcher.group(1) != null ? settingsMatcher.group(1) : settingsMatcher.group(2);
|
||||
resolvedSibling = rootDir.resolve(customPath).normalize();
|
||||
log.info("Found custom projectDir in settings for {}: {}", gradlePath, resolvedSibling);
|
||||
}
|
||||
}
|
||||
|
||||
if (Files.exists(resolvedSibling)) {
|
||||
log.info("Discovered local Gradle sibling dependency: {}", resolvedSibling);
|
||||
siblings.add(resolvedSibling);
|
||||
@@ -117,7 +148,7 @@ public class SiblingDependencyResolver {
|
||||
if (artifactId != null && targetArtifacts.contains(artifactId)) {
|
||||
Path siblingDir = p.getParent();
|
||||
if (!siblingDir.equals(projectDir)) {
|
||||
log.info("Discovered local Maven sibling dependency: {}", siblingDir);
|
||||
log.info("Discovered local Maven sibling dependency via artifact match: {}", siblingDir);
|
||||
siblings.add(siblingDir);
|
||||
}
|
||||
}
|
||||
@@ -128,6 +159,37 @@ public class SiblingDependencyResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also directly add aggregator <module> directories declared in this pom
|
||||
Matcher moduleMatcher = Pattern.compile("<module>(.*?)</module>").matcher(content);
|
||||
while (moduleMatcher.find()) {
|
||||
String moduleName = moduleMatcher.group(1).trim();
|
||||
Path moduleDir = projectDir.resolve(moduleName).normalize();
|
||||
if (Files.exists(moduleDir) && Files.exists(moduleDir.resolve("pom.xml")) && !moduleDir.equals(projectDir)) {
|
||||
log.info("Discovered local Maven sibling dependency via <module>: {}", moduleDir);
|
||||
siblings.add(moduleDir);
|
||||
}
|
||||
}
|
||||
|
||||
// Also add parent dir if it has a pom.xml
|
||||
Path parentDir = projectDir.getParent();
|
||||
|
||||
// Check for explicit <relativePath> in parent block
|
||||
Matcher parentRelativePathMatcher = Pattern.compile("<parent>.*?<relativePath>(.*?)</relativePath>.*?</parent>", Pattern.DOTALL).matcher(content);
|
||||
if (parentRelativePathMatcher.find()) {
|
||||
String relPathStr = parentRelativePathMatcher.group(1).trim();
|
||||
Path relPath = projectDir.resolve(relPathStr).normalize();
|
||||
if (Files.isRegularFile(relPath) && relPath.getFileName().toString().equals("pom.xml")) {
|
||||
parentDir = relPath.getParent();
|
||||
} else if (Files.isDirectory(relPath)) {
|
||||
parentDir = relPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (parentDir != null && Files.exists(parentDir.resolve("pom.xml")) && !parentDir.equals(projectDir)) {
|
||||
log.info("Discovered local Maven parent sibling: {}", parentDir);
|
||||
siblings.add(parentDir);
|
||||
}
|
||||
}
|
||||
return siblings;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,40 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
protected final VariableTracer variableTracer;
|
||||
protected final ConstantExtractor constantExtractor;
|
||||
protected final ConstructorAnalyzer constructorAnalyzer;
|
||||
private final Map<String, ASTNode> parsedNodeCache = new HashMap<>();
|
||||
|
||||
private ASTNode parseExpressionString(String expr) {
|
||||
if (expr == null) return null;
|
||||
return parsedNodeCache.computeIfAbsent(expr, e -> {
|
||||
ASTParser parser = ASTParser.newParser(AST.JLS17);
|
||||
Map<String, String> options = org.eclipse.jdt.core.JavaCore.getOptions();
|
||||
org.eclipse.jdt.core.JavaCore.setComplianceOptions(org.eclipse.jdt.core.JavaCore.VERSION_17, options);
|
||||
parser.setCompilerOptions(options);
|
||||
parser.setKind(ASTParser.K_EXPRESSION);
|
||||
parser.setSource(e.toCharArray());
|
||||
try {
|
||||
ASTNode node = parser.createAST(null);
|
||||
if (node instanceof org.eclipse.jdt.core.dom.CompilationUnit) {
|
||||
ASTParser fallbackParser = ASTParser.newParser(AST.JLS17);
|
||||
fallbackParser.setCompilerOptions(options);
|
||||
fallbackParser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
fallbackParser.setSource(("class A { Object o = " + e + "; }").toCharArray());
|
||||
org.eclipse.jdt.core.dom.CompilationUnit cu = (org.eclipse.jdt.core.dom.CompilationUnit) fallbackParser.createAST(null);
|
||||
if (!cu.types().isEmpty()) {
|
||||
org.eclipse.jdt.core.dom.TypeDeclaration td = (org.eclipse.jdt.core.dom.TypeDeclaration) cu.types().get(0);
|
||||
if (!td.bodyDeclarations().isEmpty() && td.bodyDeclarations().get(0) instanceof org.eclipse.jdt.core.dom.FieldDeclaration fd) {
|
||||
if (!fd.fragments().isEmpty() && fd.fragments().get(0) instanceof org.eclipse.jdt.core.dom.VariableDeclarationFragment vdf) {
|
||||
return vdf.getInitializer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
protected interface RhsResolver {
|
||||
@@ -51,13 +85,21 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
|
||||
for (EntryPoint ep : entryPoints) {
|
||||
String startMethod = ep.getClassName() + "." + ep.getMethodName();
|
||||
List<String> startMethods = new ArrayList<>();
|
||||
startMethods.add(startMethod);
|
||||
|
||||
boolean foundAny = false;
|
||||
for (TriggerPoint tp : triggers) {
|
||||
String targetMethod = tp.getClassName() + "." + tp.getMethodName();
|
||||
List<String> path = pathFinder.findPath(startMethod, targetMethod, callGraph, new HashSet<>());
|
||||
if (path != null) {
|
||||
List<List<String>> allPaths = new ArrayList<>();
|
||||
for (String sMethod : startMethods) {
|
||||
allPaths.addAll(pathFinder.findAllPaths(sMethod, targetMethod, callGraph, new HashSet<>()));
|
||||
}
|
||||
Set<List<String>> uniquePaths = new LinkedHashSet<>(allPaths);
|
||||
for (List<String> path : uniquePaths) {
|
||||
foundAny = true;
|
||||
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph);
|
||||
if (resolvedTp != null) {
|
||||
String contextMachineId = pathFinder.extractContextMachineId(path, callGraph);
|
||||
chains.add(CallChain.builder()
|
||||
.entryPoint(ep)
|
||||
@@ -67,6 +109,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundAny && log.isDebugEnabled()) {
|
||||
log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet());
|
||||
}
|
||||
@@ -75,23 +118,81 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
protected TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||
if (path.size() < 2) return tp;
|
||||
if (path.size() < 2) {
|
||||
boolean isExternal = isExternalParameter(tp.getClassName() + "." + tp.getMethodName(), tp.getEvent());
|
||||
return tp.toBuilder().external(isExternal).constraint(tp.getConstraint()).build();
|
||||
}
|
||||
|
||||
String event = tp.getEvent();
|
||||
if (event == null || event.isEmpty() || event.contains("\"")) return tp;
|
||||
if (event == null || event.isEmpty() || event.contains("\"")) {
|
||||
return tp;
|
||||
}
|
||||
|
||||
String[] finalParamNameRef = { event };
|
||||
TriggerPoint resolved = resolveTriggerPointParametersOriginal(tp, path, callGraph, finalParamNameRef);
|
||||
if (resolved == null) return null;
|
||||
String entryMethod = path.isEmpty() ? (tp.getClassName() + "." + tp.getMethodName()) : path.get(0);
|
||||
boolean isExternal = isExternalParameter(entryMethod, finalParamNameRef[0]);
|
||||
|
||||
String pathConstraint = extractPathConstraints(path, callGraph);
|
||||
String triggerConstraint = resolved.getConstraint();
|
||||
String finalConstraint = null;
|
||||
if (pathConstraint != null && triggerConstraint != null) {
|
||||
finalConstraint = pathConstraint + " && " + triggerConstraint;
|
||||
} else if (pathConstraint != null) {
|
||||
finalConstraint = pathConstraint;
|
||||
} else {
|
||||
finalConstraint = triggerConstraint;
|
||||
}
|
||||
|
||||
return resolved.toBuilder().external(isExternal).constraint(finalConstraint).build();
|
||||
}
|
||||
|
||||
protected TriggerPoint resolveTriggerPointParametersOriginal(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph, String[] finalParamNameRef) {
|
||||
click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel.setCurrentPath(path);
|
||||
try {
|
||||
String event = tp.getEvent();
|
||||
event = unwrapConverterMethods(event);
|
||||
String currentParamName = event;
|
||||
String resolvedValue = event;
|
||||
String methodSuffix = "";
|
||||
|
||||
String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix);
|
||||
currentParamName = extractedEntry[0];
|
||||
methodSuffix = extractedEntry[1];
|
||||
boolean isAmbiguous = false;
|
||||
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||
|
||||
for (int i = path.size() - 1; i > 0; i--) {
|
||||
String target = path.get(i);
|
||||
String caller = path.get(i - 1);
|
||||
int paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
if (paramIndex < 0) {
|
||||
if ("this".equals(currentParamName)) {
|
||||
String actualReceiver = null;
|
||||
for (int j = i; j > 0; j--) {
|
||||
String t = path.get(j);
|
||||
String c = path.get(j - 1);
|
||||
List<CallEdge> edges = callGraph.get(c);
|
||||
if (edges != null) {
|
||||
for (CallEdge edge : edges) {
|
||||
if (edge.getTargetMethod().equals(t) || pathFinder.isHeuristicMatch(edge.getTargetMethod(), t)) {
|
||||
if (edge.getReceiver() != null && !edge.getReceiver().isEmpty()) {
|
||||
actualReceiver = edge.getReceiver();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actualReceiver != null) break;
|
||||
}
|
||||
if (actualReceiver != null) {
|
||||
currentParamName = actualReceiver;
|
||||
resolvedValue = actualReceiver + methodSuffix;
|
||||
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||
paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
}
|
||||
}
|
||||
|
||||
if (paramIndex < 0) {
|
||||
Map<String, String> parameterValues = variableTracer.buildParameterValuesMap(caller, target, callGraph, path, i);
|
||||
String tracedVar = variableTracer.traceLocalVariable(target, currentParamName, parameterValues);
|
||||
@@ -104,6 +205,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
paramIndex = typeResolver.getParameterIndex(target, currentParamName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramIndex < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -133,6 +235,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (paramIndex < edge.getArguments().size()) {
|
||||
String arg = edge.getArguments().get(paramIndex);
|
||||
if (arg != null) {
|
||||
arg = unwrapConverterMethods(arg);
|
||||
String receiver = edge.getReceiver();
|
||||
String actualReceiver = null;
|
||||
if ("this".equals(arg) || arg.startsWith("this.") || "super".equals(arg) || arg.startsWith("super.")) {
|
||||
@@ -160,6 +263,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
arg = extractedArg[0];
|
||||
methodSuffix = extractedArg[1];
|
||||
currentParamName = arg;
|
||||
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||
resolvedValue = arg + methodSuffix;
|
||||
found = true;
|
||||
break;
|
||||
@@ -168,6 +272,20 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Bridge polymorphic interfaces to implementations without graph edges
|
||||
String callerSimple = caller.contains(".") ? caller.substring(caller.lastIndexOf('.') + 1) : caller;
|
||||
String targetSimple = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||
if (callerSimple.equals(targetSimple)) {
|
||||
String interfaceParamName = typeResolver.getParameterName(caller, paramIndex);
|
||||
if (interfaceParamName != null) {
|
||||
currentParamName = interfaceParamName;
|
||||
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||
resolvedValue = currentParamName + methodSuffix;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
}
|
||||
|
||||
@@ -184,6 +302,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
constantExtractor.extractConstantsFromExpression(tracedSetter, setterEvents);
|
||||
}
|
||||
if (!setterEvents.isEmpty()) {
|
||||
log.debug("Early return 1: setterEvents = {}", setterEvents);
|
||||
return TriggerPoint.builder()
|
||||
.event(tp.getEvent())
|
||||
.className(tp.getClassName())
|
||||
@@ -194,16 +313,21 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(setterEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasGetterOnReceiver = methodSuffix.startsWith(".get") && currentParamName != null
|
||||
&& !currentParamName.contains(".") && !currentParamName.contains("(") && !currentParamName.contains("new ");
|
||||
boolean hasGetterOnReceiver = (methodSuffix.startsWith(".get") || methodSuffix.equals(".type") || methodSuffix.equals(".event") || methodSuffix.equals(".type()") || methodSuffix.equals(".event()")) && currentParamName != null
|
||||
&& (currentParamName.contains("new ") || (!currentParamName.replaceFirst("^this\\.", "").contains(".") && !currentParamName.contains("(")));
|
||||
if (hasGetterOnReceiver) {
|
||||
List<String> getterEvents = evaluateGetterOnReceiver(resolvedValue, methodSuffix, entryMethod, path, callGraph);
|
||||
if (getterEvents != null && !getterEvents.isEmpty()) {
|
||||
log.debug("Early return 2: getterEvents = {}", getterEvents);
|
||||
return TriggerPoint.builder()
|
||||
.event(tp.getEvent())
|
||||
.className(tp.getClassName())
|
||||
@@ -214,6 +338,10 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(getterEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -224,10 +352,12 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
tracedVar = extractedFinalTraced[0];
|
||||
methodSuffix = extractedFinalTraced[1];
|
||||
currentParamName = tracedVar;
|
||||
if (finalParamNameRef != null) finalParamNameRef[0] = currentParamName;
|
||||
resolvedValue = tracedVar + methodSuffix;
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("resolvedValue before ENUM_SET check: {} for path: {}", resolvedValue, path);
|
||||
List<String> polymorphicEvents = new ArrayList<>();
|
||||
|
||||
if (resolvedValue.startsWith("ENUM_SET:")) {
|
||||
@@ -245,14 +375,17 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(polymorphicEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.build();
|
||||
}
|
||||
|
||||
ASTParser exprParser = ASTParser.newParser(AST.JLS17);
|
||||
exprParser.setKind(ASTParser.K_EXPRESSION);
|
||||
exprParser.setSource(resolvedValue.toCharArray());
|
||||
ASTNode exprNode = exprParser.createAST(null);
|
||||
System.out.println("DEBUG resolvedValue=" + resolvedValue + ", exprNode=" + exprNode.getClass().getSimpleName());
|
||||
ASTNode exprNode = parseExpressionString(resolvedValue);
|
||||
while (exprNode instanceof ParenthesizedExpression pe) {
|
||||
exprNode = pe.getExpression();
|
||||
}
|
||||
|
||||
String varName = null;
|
||||
String methodName = null;
|
||||
@@ -261,6 +394,31 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
|
||||
if (exprNode instanceof MethodInvocation mi) {
|
||||
methodName = mi.getName().getIdentifier();
|
||||
boolean handledStaticEnum = false;
|
||||
if (methodName.equals("valueOf") && mi.arguments().size() == 1) {
|
||||
Object arg = mi.arguments().get(0);
|
||||
if (arg instanceof StringLiteral sl) {
|
||||
polymorphicEvents.add(sl.getLiteralValue());
|
||||
methodName = null;
|
||||
handledStaticEnum = true;
|
||||
} else if (arg instanceof QualifiedName qn) {
|
||||
polymorphicEvents.add(qn.getName().getIdentifier());
|
||||
methodName = null;
|
||||
handledStaticEnum = true;
|
||||
}
|
||||
} else if (methodName.equals("name") && mi.arguments().isEmpty()) {
|
||||
if (mi.getExpression() instanceof QualifiedName qn) {
|
||||
polymorphicEvents.add(qn.getName().getIdentifier());
|
||||
methodName = null;
|
||||
handledStaticEnum = true;
|
||||
} else if (mi.getExpression() instanceof SimpleName sn) {
|
||||
polymorphicEvents.add(sn.getIdentifier());
|
||||
methodName = null;
|
||||
handledStaticEnum = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handledStaticEnum) {
|
||||
if (mi.getExpression() instanceof SimpleName sn) {
|
||||
varName = sn.getIdentifier();
|
||||
} else if (mi.getExpression() instanceof ParenthesizedExpression pe &&
|
||||
@@ -272,6 +430,37 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
} else if (mi.getExpression() instanceof ClassInstanceCreation cic) {
|
||||
declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||
sourceMethod = "inline-instantiation";
|
||||
|
||||
List<String> getterResults = constructorAnalyzer.resolveInlineInstantiationGetterResult(cic, methodName, context, new java.util.HashSet<>());
|
||||
if (getterResults != null && !getterResults.isEmpty()) {
|
||||
for (String gr : getterResults) {
|
||||
if (gr.startsWith("ENUM_SET:")) {
|
||||
for (String eVal : gr.substring(9).split(",")) {
|
||||
String parsed = constantExtractor.parseEnumSetElement(eVal);
|
||||
if (!polymorphicEvents.contains(parsed)) polymorphicEvents.add(parsed);
|
||||
}
|
||||
} else {
|
||||
if (!polymorphicEvents.contains(gr)) polymorphicEvents.add(gr);
|
||||
}
|
||||
}
|
||||
return TriggerPoint.builder()
|
||||
.event(tp.getEvent())
|
||||
.className(tp.getClassName())
|
||||
.methodName(tp.getMethodName())
|
||||
.sourceFile(tp.getSourceFile())
|
||||
.sourceModule(tp.getSourceModule())
|
||||
.stateMachineId(tp.getStateMachineId())
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(polymorphicEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.ambiguous(isAmbiguous)
|
||||
.build();
|
||||
}
|
||||
|
||||
if (cic.getAnonymousClassDeclaration() != null) {
|
||||
final List<String> polyEventsRef = polymorphicEvents;
|
||||
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
||||
@@ -307,27 +496,74 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
varName = exprStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (exprNode instanceof ClassInstanceCreation cic) {
|
||||
declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||
sourceMethod = "inline-instantiation";
|
||||
polymorphicEvents.add(declaredType);
|
||||
} else if (exprNode instanceof ConditionalExpression cond) {
|
||||
if (cond.getThenExpression() instanceof ClassInstanceCreation cicThen) {
|
||||
polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicThen.getType()));
|
||||
List<Expression> branches = new ArrayList<>();
|
||||
branches.add(cond.getThenExpression());
|
||||
branches.add(cond.getElseExpression());
|
||||
for (Expression branch : branches) {
|
||||
List<Expression> tracedBranch = variableTracer.traceVariableAll(branch);
|
||||
for (Expression tb : tracedBranch) {
|
||||
if (tb instanceof ClassInstanceCreation cic) {
|
||||
polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()));
|
||||
} else {
|
||||
constantExtractor.extractConstantsFromExpression(tb, polymorphicEvents);
|
||||
}
|
||||
}
|
||||
if (cond.getElseExpression() instanceof ClassInstanceCreation cicElse) {
|
||||
polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicElse.getType()));
|
||||
}
|
||||
sourceMethod = "inline-ternary";
|
||||
declaredType = polymorphicEvents.isEmpty() ? null : polymorphicEvents.get(0);
|
||||
} else if (exprNode instanceof SwitchExpression swExpr) {
|
||||
List<String> polyEventsRef = polymorphicEvents;
|
||||
swExpr.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(YieldStatement ys) {
|
||||
List<Expression> tracedBranch = variableTracer.traceVariableAll(ys.getExpression());
|
||||
for (Expression tb : tracedBranch) {
|
||||
if (tb instanceof ClassInstanceCreation cic) {
|
||||
polyEventsRef.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()));
|
||||
} else {
|
||||
constantExtractor.extractConstantsFromExpression(tb, polyEventsRef);
|
||||
}
|
||||
}
|
||||
return super.visit(ys);
|
||||
}
|
||||
@Override
|
||||
public boolean visit(ExpressionStatement es) {
|
||||
List<Expression> tracedBranch = variableTracer.traceVariableAll(es.getExpression());
|
||||
for (Expression tb : tracedBranch) {
|
||||
if (tb instanceof ClassInstanceCreation cic) {
|
||||
polyEventsRef.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()));
|
||||
} else {
|
||||
constantExtractor.extractConstantsFromExpression(tb, polyEventsRef);
|
||||
}
|
||||
}
|
||||
return super.visit(es);
|
||||
}
|
||||
});
|
||||
sourceMethod = "inline-switch";
|
||||
declaredType = polymorphicEvents.isEmpty() ? null : polymorphicEvents.get(0);
|
||||
} else if (exprNode instanceof SimpleName sn) {
|
||||
varName = sn.getIdentifier();
|
||||
if (varName.equals(varName.toUpperCase()) && varName.contains("_")) {
|
||||
if (varName.equals(varName.toUpperCase())) {
|
||||
polymorphicEvents.add(varName);
|
||||
methodName = null;
|
||||
} else {
|
||||
methodName = "VariableReference";
|
||||
}
|
||||
} else if (exprNode instanceof QualifiedName qn) {
|
||||
polymorphicEvents.add(qn.getFullyQualifiedName());
|
||||
methodName = null;
|
||||
} else if (exprNode instanceof FieldAccess fa) {
|
||||
polymorphicEvents.add(fa.toString());
|
||||
methodName = null;
|
||||
} else if (exprNode instanceof StringLiteral sl) {
|
||||
polymorphicEvents.add(sl.getLiteralValue());
|
||||
methodName = null;
|
||||
} else if (exprNode instanceof ParenthesizedExpression pe &&
|
||||
pe.getExpression() instanceof CastExpression ce &&
|
||||
ce.getExpression() instanceof ClassInstanceCreation cic) {
|
||||
@@ -352,7 +588,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
resolvedValue = cic.toString() + "." + methodName + "()";
|
||||
}
|
||||
}
|
||||
System.out.println("Returning early with events: " + polymorphicEvents + " for " + resolvedValue); if (!polymorphicEvents.isEmpty()) {
|
||||
if (!polymorphicEvents.isEmpty()) {
|
||||
return TriggerPoint.builder()
|
||||
.event(resolvedValue)
|
||||
.className(tp.getClassName())
|
||||
@@ -363,6 +599,11 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.sourceState(tp.getSourceState())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.polymorphicEvents(polymorphicEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.ambiguous(isAmbiguous)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -412,10 +653,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (extractedFinalTraced[0] != null) {
|
||||
resolvedValue = extractedFinalTraced[0] + extractedFinalTraced[1];
|
||||
if (extractedFinalTraced[0].contains("new ") && extractedFinalTraced[0].contains("{")) {
|
||||
ASTParser p = ASTParser.newParser(AST.JLS17);
|
||||
p.setKind(ASTParser.K_EXPRESSION);
|
||||
p.setSource(resolvedValue.toCharArray());
|
||||
ASTNode newNode = p.createAST(null);
|
||||
ASTNode newNode = parseExpressionString(resolvedValue);
|
||||
if (newNode instanceof Expression) {
|
||||
List<Expression> traced = variableTracer.traceVariableAll((Expression) newNode);
|
||||
for (Expression ex : traced) {
|
||||
@@ -435,16 +673,13 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.')));
|
||||
CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
|
||||
List<String> values = constantExtractor.resolveMethodReturnConstant(declaredType, methodName, 0, new HashSet<>(), cu);
|
||||
System.out.println("RESOLVED VALUES FOR " + methodName + " in " + currentTd.getName() + ": " + values); if (values != null && !values.isEmpty()) {
|
||||
if (values != null && !values.isEmpty()) {
|
||||
polymorphicEvents.addAll(values);
|
||||
}
|
||||
if (polymorphicEvents.isEmpty() && variableTracer != null) {
|
||||
String initializer = variableTracer.traceLocalVariable(methodFqn, varName);
|
||||
if (initializer != null && !initializer.equals(varName)) {
|
||||
ASTParser mapParser = ASTParser.newParser(AST.JLS17);
|
||||
mapParser.setKind(ASTParser.K_EXPRESSION);
|
||||
mapParser.setSource(initializer.toCharArray());
|
||||
ASTNode mapNode = mapParser.createAST(null);
|
||||
ASTNode mapNode = parseExpressionString(initializer);
|
||||
if (mapNode instanceof Expression) {
|
||||
List<Expression> mapTraced = variableTracer.traceVariableAll((Expression) mapNode);
|
||||
for (Expression t : mapTraced) {
|
||||
@@ -476,7 +711,6 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
if (currentTd != null && context.findMethodDeclaration(currentTd, methodName, true) != null) {
|
||||
CompilationUnit cu = currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
|
||||
List<String> values = constantExtractor.resolveMethodReturnConstant(context.getFqn(currentTd), methodName, 0, new HashSet<>(), cu);
|
||||
System.out.println("DEBUG RESOLVED VALUES FOR " + methodName + " in " + currentTd.getName() + " -> " + values);
|
||||
if (values != null && !values.isEmpty()) {
|
||||
polymorphicEvents.addAll(values);
|
||||
sourceMethod = methodFqn;
|
||||
@@ -486,12 +720,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("declaredType before getEnumValues: {} for exprNode: {} in {}", declaredType, exprNode, entryMethod);
|
||||
if (declaredType != null) {
|
||||
List<String> enumValues = context.getEnumValues(declaredType);
|
||||
if (enumValues != null && !enumValues.isEmpty()) {
|
||||
for (String ev : enumValues) {
|
||||
polymorphicEvents.add(constantExtractor.parseEnumSetElement(ev));
|
||||
}
|
||||
isAmbiguous = true;
|
||||
} else {
|
||||
List<String> typesToInspect = new ArrayList<>();
|
||||
if ("inline-instantiation".equals(sourceMethod)) {
|
||||
@@ -588,6 +824,30 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
});
|
||||
}
|
||||
|
||||
List<String> unpacked = new ArrayList<>();
|
||||
for (String ev : polymorphicEvents) {
|
||||
if (ev != null) {
|
||||
if (ev.startsWith("ENUM_SET:")) {
|
||||
for (String part : ev.substring(9).split(",")) {
|
||||
String clean = constantExtractor.parseEnumSetElement(part);
|
||||
if (clean != null && !unpacked.contains(clean)) {
|
||||
unpacked.add(clean);
|
||||
}
|
||||
}
|
||||
} else if (ev.startsWith("<SYMBOLIC:")) {
|
||||
if (!unpacked.contains(ev)) {
|
||||
unpacked.add(ev);
|
||||
}
|
||||
} else {
|
||||
String clean = constantExtractor.parseEnumSetElement(ev);
|
||||
if (clean != null && !unpacked.contains(clean)) {
|
||||
unpacked.add(clean);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
polymorphicEvents = unpacked;
|
||||
|
||||
polymorphicEvents = polymorphicEvents.stream().distinct().collect(java.util.stream.Collectors.toList());
|
||||
|
||||
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
||||
@@ -597,11 +857,21 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
.methodName(tp.getMethodName())
|
||||
.sourceFile(tp.getSourceFile())
|
||||
.lineNumber(tp.getLineNumber())
|
||||
.sourceModule(tp.getSourceModule())
|
||||
.stateMachineId(tp.getStateMachineId())
|
||||
.sourceState(tp.getSourceState())
|
||||
.polymorphicEvents(polymorphicEvents)
|
||||
.constraint(tp.getConstraint())
|
||||
.stateTypeFqn(tp.getStateTypeFqn())
|
||||
.eventTypeFqn(tp.getEventTypeFqn())
|
||||
.external(tp.isExternal())
|
||||
.build();
|
||||
}
|
||||
|
||||
return tp;
|
||||
} finally {
|
||||
click.kamil.springstatemachineexporter.ast.common.JdtDataFlowModel.clearCurrentPath();
|
||||
}
|
||||
}
|
||||
|
||||
protected MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||
@@ -662,19 +932,32 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) {
|
||||
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||
TypeDeclaration td = context.getTypeDeclaration(typeName);
|
||||
boolean shouldUnwrap = true;
|
||||
if (td != null) {
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
if (!md.isConstructor()) {
|
||||
shouldUnwrap = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shouldUnwrap) {
|
||||
Expression firstArg = (Expression) cic.arguments().get(0);
|
||||
String resolved = constantResolver.resolve(firstArg, context);
|
||||
if (resolved != null) {
|
||||
expr = firstArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String val = constantResolver.resolve(expr, context);
|
||||
if (val == null) {
|
||||
val = expr.toString();
|
||||
}
|
||||
|
||||
System.out.println("DEBUG resolveArgument expr=" + expr + ", val=" + val); return postProcessResolvedArgument(expr, val);
|
||||
return postProcessResolvedArgument(expr, val);
|
||||
}
|
||||
|
||||
protected String postProcessResolvedArgument(Expression originalExpr, String resolvedValue) {
|
||||
@@ -748,10 +1031,10 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
protected List<String> evaluateGetterOnReceiver(String resolvedValue, String methodSuffix, String entryMethod, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||
ASTParser exprParser = ASTParser.newParser(AST.JLS17);
|
||||
exprParser.setKind(ASTParser.K_EXPRESSION);
|
||||
exprParser.setSource(resolvedValue.toCharArray());
|
||||
ASTNode exprNode = exprParser.createAST(null);
|
||||
ASTNode exprNode = parseExpressionString(resolvedValue);
|
||||
while (exprNode instanceof ParenthesizedExpression pe) {
|
||||
exprNode = pe.getExpression();
|
||||
}
|
||||
|
||||
if (exprNode instanceof SuperMethodInvocation smi) {
|
||||
String getterName = smi.getName().getIdentifier();
|
||||
@@ -768,10 +1051,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
Expression receiver = mi.getExpression();
|
||||
String receiverName = null;
|
||||
String receiverType = null;
|
||||
ClassInstanceCreation directCic = null;
|
||||
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
receiverName = sn.getIdentifier();
|
||||
receiverType = variableTracer.getVariableDeclaredType(entryMethod, receiverName);
|
||||
} else if (receiver instanceof ClassInstanceCreation cic) {
|
||||
directCic = cic;
|
||||
receiverType = cic.getType().toString();
|
||||
} else if (receiver instanceof MethodInvocation receiverMi) {
|
||||
Expression currentMi = receiverMi;
|
||||
while (currentMi instanceof MethodInvocation chainMi) {
|
||||
@@ -816,15 +1103,35 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
simpleReceiverType = rawType;
|
||||
}
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType);
|
||||
CompilationUnit contextCu = null;
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String className = entryMethod.substring(0, lastDot);
|
||||
TypeDeclaration entryTd = context.getTypeDeclaration(className);
|
||||
if (entryTd != null) {
|
||||
contextCu = (CompilationUnit) entryTd.getRoot();
|
||||
}
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(simpleReceiverType, contextCu);
|
||||
if (td == null) {
|
||||
td = context.getTypeDeclaration(simpleReceiverType);
|
||||
}
|
||||
if (td != null) {
|
||||
Map<String, String> fieldValues = new HashMap<>();
|
||||
String varName = receiverName;
|
||||
ClassInstanceCreation cic = null;
|
||||
if (directCic != null) {
|
||||
cic = directCic;
|
||||
} else {
|
||||
Expression initExpr = findVariableInitializer(entryMethod, varName);
|
||||
if (initExpr instanceof MethodInvocation initMi) {
|
||||
initExpr = unwrapMethodInvocation(initMi, 0, entryMethod);
|
||||
}
|
||||
if (!(initExpr instanceof ClassInstanceCreation cic)) {
|
||||
if (initExpr instanceof ClassInstanceCreation) {
|
||||
cic = (ClassInstanceCreation) initExpr;
|
||||
}
|
||||
}
|
||||
if (cic == null) {
|
||||
return null;
|
||||
}
|
||||
MethodDeclaration getter = null;
|
||||
@@ -841,9 +1148,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
if (getter != null && getter.getBody() != null) {
|
||||
fieldValues.putAll(constructorAnalyzer.buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor));
|
||||
int lastDot = entryMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String className = entryMethod.substring(0, lastDot);
|
||||
int lastDotEntry = entryMethod.lastIndexOf('.');
|
||||
if (lastDotEntry > 0) {
|
||||
String className = entryMethod.substring(0, lastDotEntry);
|
||||
String methodName = entryMethod.substring(lastDot + 1);
|
||||
TypeDeclaration entryTd = context.getTypeDeclaration(className);
|
||||
if (entryTd != null) {
|
||||
@@ -904,12 +1211,26 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
protected String[] extractMethodSuffix(String paramName, String currentSuffix) {
|
||||
if (paramName != null && !paramName.contains(" ") && !paramName.startsWith("new ")) {
|
||||
int bracketIndex = paramName.indexOf('[');
|
||||
int dotIndex = paramName.indexOf('.');
|
||||
int dotIndex = -1;
|
||||
int parenDepth = 0;
|
||||
for (int i = 0; i < paramName.length(); i++) {
|
||||
char c = paramName.charAt(i);
|
||||
if (c == '(') parenDepth++;
|
||||
else if (c == ')') parenDepth--;
|
||||
else if (c == '.' && parenDepth == 0) {
|
||||
dotIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bracketIndex > 0 && (dotIndex == -1 || bracketIndex < dotIndex)) {
|
||||
return new String[] { paramName.substring(0, bracketIndex), paramName.substring(bracketIndex) + currentSuffix };
|
||||
} else if (dotIndex > 0) {
|
||||
if (paramName.startsWith("this.")) {
|
||||
String afterThis = paramName.substring(5);
|
||||
if ((afterThis.startsWith("get") || afterThis.startsWith("is") || afterThis.contains("(")) && !afterThis.contains(".")) {
|
||||
return new String[] { "this", "." + afterThis + currentSuffix };
|
||||
}
|
||||
int nextDotIndex = paramName.indexOf('.', 5);
|
||||
if (nextDotIndex > 0) {
|
||||
return new String[] { paramName.substring(0, nextDotIndex), paramName.substring(nextDotIndex) + currentSuffix };
|
||||
@@ -1012,6 +1333,290 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
||||
return constantExtractor.resolveClassConstantReturns(className, context, contextCu);
|
||||
}
|
||||
|
||||
private boolean isExternalParameter(String entryMethod, String paramName) {
|
||||
if (entryMethod == null || paramName == null || !entryMethod.contains(".")) return false;
|
||||
String className = entryMethod.substring(0, entryMethod.lastIndexOf('.'));
|
||||
String methodName = entryMethod.substring(entryMethod.lastIndexOf('.') + 1);
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td != null) {
|
||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||
if (md != null) {
|
||||
for (Object paramObj : md.parameters()) {
|
||||
if (paramObj instanceof SingleVariableDeclaration svd) {
|
||||
if (svd.getName().getIdentifier().equals(paramName)) {
|
||||
for (Object modifier : svd.modifiers()) {
|
||||
if (modifier instanceof Annotation annotation) {
|
||||
String typeName = annotation.getTypeName().getFullyQualifiedName();
|
||||
if (typeName.endsWith("PathVariable") || typeName.endsWith("RequestBody") || typeName.endsWith("RequestParam")
|
||||
|| typeName.endsWith("PathParam") || typeName.endsWith("QueryParam")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String extractPathConstraints(List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||
List<String> constraints = new ArrayList<>();
|
||||
for (int i = 0; i < path.size() - 1; i++) {
|
||||
String caller = path.get(i);
|
||||
String target = path.get(i + 1);
|
||||
List<CallEdge> edges = callGraph.get(caller);
|
||||
if (edges != null) {
|
||||
for (CallEdge edge : edges) {
|
||||
if (edge.getTargetMethod().equals(target) || pathFinder.isHeuristicMatch(edge.getTargetMethod(), target)) {
|
||||
if (edge.getConstraint() != null && !edge.getConstraint().isEmpty()) {
|
||||
constraints.add(edge.getConstraint());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (constraints.isEmpty()) return null;
|
||||
return String.join(" && ", constraints);
|
||||
}
|
||||
|
||||
protected String resolveConstraint(MethodInvocation node, String calledMethod, String baseConstraint) {
|
||||
String resolvedConstraint = baseConstraint;
|
||||
if (node.getExpression() instanceof MethodInvocation miReceiver) {
|
||||
if (miReceiver.getName().getIdentifier().equals("get") && !miReceiver.arguments().isEmpty() && miReceiver.getExpression() != null) {
|
||||
Expression keyExpr = (Expression) miReceiver.arguments().get(0);
|
||||
String mapKeyVar = keyExpr.toString();
|
||||
Map<String, String> keyToType = findMapPopulatedKeys(node, miReceiver.getExpression().toString());
|
||||
|
||||
int lastDot = calledMethod.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String impl = calledMethod.substring(0, lastDot);
|
||||
for (Map.Entry<String, String> entry : keyToType.entrySet()) {
|
||||
String registeredType = entry.getValue();
|
||||
if (registeredType.equals(impl) || context.getImplementations(registeredType).contains(impl) || context.getImplementations(impl).contains(registeredType)) {
|
||||
String mapConstraint = mapKeyVar + " == \"" + entry.getKey() + "\"";
|
||||
if (resolvedConstraint != null && !resolvedConstraint.isEmpty()) {
|
||||
resolvedConstraint = resolvedConstraint + " && " + mapConstraint;
|
||||
} else {
|
||||
resolvedConstraint = mapConstraint;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolvedConstraint;
|
||||
}
|
||||
|
||||
protected String resolveMapValueType(MethodInvocation mi) {
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver == null) return null;
|
||||
|
||||
ITypeBinding binding = receiver.resolveTypeBinding();
|
||||
if (binding != null && binding.getTypeArguments().length >= 2) {
|
||||
ITypeBinding valBinding = binding.getTypeArguments()[1];
|
||||
return valBinding.getErasure().getQualifiedName();
|
||||
}
|
||||
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
|
||||
if (enclosingMethod != null) {
|
||||
for (Object paramObj : enclosingMethod.parameters()) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj;
|
||||
if (svd.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
return extractMapValueTypeFromString(svd.getType().toString(), sn);
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDeclaration enclosingType = findEnclosingType(sn);
|
||||
if (enclosingType != null) {
|
||||
for (FieldDeclaration fd : enclosingType.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
if (fragObj instanceof VariableDeclarationFragment vdf && vdf.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
return extractMapValueTypeFromString(fd.getType().toString(), sn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String extractMapValueTypeFromString(String typeStr, ASTNode contextNode) {
|
||||
if (typeStr.contains("<")) {
|
||||
String generics = typeStr.substring(typeStr.indexOf('<') + 1, typeStr.lastIndexOf('>'));
|
||||
String[] parts = generics.split(",");
|
||||
if (parts.length >= 2) {
|
||||
String valType = parts[1].trim();
|
||||
TypeDeclaration td = context.getTypeDeclaration(valType);
|
||||
if (td == null) {
|
||||
CompilationUnit cu = (CompilationUnit) contextNode.getRoot();
|
||||
td = context.getTypeDeclaration(valType, cu);
|
||||
}
|
||||
if (td != null) {
|
||||
return context.getFqn(td);
|
||||
}
|
||||
return valType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Map<String, String> findMapPopulatedKeys(ASTNode contextNode, String mapVarName) {
|
||||
Map<String, String> keyToType = new HashMap<>();
|
||||
TypeDeclaration td = findEnclosingType(contextNode);
|
||||
if (td != null) {
|
||||
td.accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(MethodInvocation node) {
|
||||
if (node.getName().getIdentifier().equals("put") && node.getExpression() != null) {
|
||||
String exprStr = node.getExpression().toString();
|
||||
if (exprStr.equals(mapVarName) || exprStr.endsWith("." + mapVarName)) {
|
||||
if (node.arguments().size() >= 2) {
|
||||
Expression keyArg = (Expression) node.arguments().get(0);
|
||||
Expression valArg = (Expression) node.arguments().get(1);
|
||||
|
||||
String keyVal = constantResolver.resolve(keyArg, context);
|
||||
if (keyVal == null) {
|
||||
keyVal = keyArg.toString().replaceAll("[\"']", "");
|
||||
}
|
||||
|
||||
String valType = null;
|
||||
ITypeBinding valBinding = valArg.resolveTypeBinding();
|
||||
if (valBinding != null) {
|
||||
valType = valBinding.getErasure().getQualifiedName();
|
||||
}
|
||||
if (valType == null && valArg instanceof SimpleName sn) {
|
||||
MethodDeclaration enclosingMethod = findEnclosingMethod(sn);
|
||||
if (enclosingMethod != null) {
|
||||
for (Object paramObj : enclosingMethod.parameters()) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj;
|
||||
if (svd.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
if (svd.getType().isSimpleType()) {
|
||||
valType = ((SimpleType) svd.getType()).getName().getFullyQualifiedName();
|
||||
} else {
|
||||
valType = svd.getType().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeDeclaration enclosingType = findEnclosingType(sn);
|
||||
if (enclosingType != null) {
|
||||
for (FieldDeclaration fd : enclosingType.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
if (fragObj instanceof VariableDeclarationFragment vdf && vdf.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||
if (fd.getType().isSimpleType()) {
|
||||
valType = ((SimpleType) fd.getType()).getName().getFullyQualifiedName();
|
||||
} else {
|
||||
valType = fd.getType().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valType != null && keyVal != null) {
|
||||
TypeDeclaration valTd = context.getTypeDeclaration(valType);
|
||||
if (valTd == null) {
|
||||
CompilationUnit cu = (CompilationUnit) node.getRoot();
|
||||
valTd = context.getTypeDeclaration(valType, cu);
|
||||
}
|
||||
if (valTd != null) {
|
||||
valType = context.getFqn(valTd);
|
||||
}
|
||||
keyToType.put(keyVal, valType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
return keyToType;
|
||||
}
|
||||
|
||||
protected abstract Map<String, List<CallEdge>> buildCallGraph();
|
||||
protected abstract String resolveCalledMethod(MethodInvocation node);
|
||||
|
||||
protected List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
|
||||
String baseCalled = resolveCalledMethod(node);
|
||||
if (baseCalled == null) return Collections.emptyList();
|
||||
|
||||
List<String> allResolved = new ArrayList<>();
|
||||
|
||||
if (node.getExpression() == null) {
|
||||
return Collections.singletonList(baseCalled);
|
||||
}
|
||||
|
||||
if (baseCalled.contains(".")) {
|
||||
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
|
||||
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
List<String> impls = context.getImplementations(className);
|
||||
|
||||
boolean shouldExpand = true;
|
||||
if (impls.isEmpty()) {
|
||||
shouldExpand = false;
|
||||
} else if (td == null) {
|
||||
// Do not polymorphically expand external generic types (e.g. java.lang.Runnable, Spring Action)
|
||||
// across the entire codebase. This prevents massive false-positive tangling.
|
||||
shouldExpand = false;
|
||||
} else {
|
||||
boolean isImplicitThis = node.getExpression() instanceof ThisExpression;
|
||||
if (!td.isInterface() && !Modifier.isAbstract(td.getModifiers()) && isImplicitThis) {
|
||||
shouldExpand = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldExpand) {
|
||||
boolean isAbstractOrInterface = td.isInterface() || Modifier.isAbstract(td.getModifiers());
|
||||
if (!isAbstractOrInterface) {
|
||||
allResolved.add(baseCalled);
|
||||
}
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
}
|
||||
} else {
|
||||
allResolved.add(baseCalled);
|
||||
}
|
||||
} else {
|
||||
allResolved.add(baseCalled);
|
||||
}
|
||||
|
||||
return allResolved;
|
||||
}
|
||||
|
||||
private String unwrapConverterMethods(String arg) {
|
||||
if (arg == null) return null;
|
||||
String current = arg;
|
||||
while (current.contains("(") && !current.startsWith("new ")) {
|
||||
org.eclipse.jdt.core.dom.ASTNode argNode = parseExpressionString(current);
|
||||
if (argNode instanceof org.eclipse.jdt.core.dom.MethodInvocation miArg && !miArg.arguments().isEmpty()) {
|
||||
boolean shouldUnpack = false;
|
||||
org.eclipse.jdt.core.dom.Expression recv = miArg.getExpression();
|
||||
if (recv == null || recv instanceof org.eclipse.jdt.core.dom.ThisExpression) {
|
||||
shouldUnpack = true;
|
||||
} else if (recv instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
||||
shouldUnpack = !Character.isLowerCase(sn.getIdentifier().charAt(0));
|
||||
} else if (recv instanceof org.eclipse.jdt.core.dom.QualifiedName qn) {
|
||||
shouldUnpack = !Character.isLowerCase(qn.getName().getIdentifier().charAt(0));
|
||||
}
|
||||
if (shouldUnpack) {
|
||||
org.eclipse.jdt.core.dom.Expression innerArg = (org.eclipse.jdt.core.dom.Expression) miArg.arguments().get(0);
|
||||
if (miArg.getName().getIdentifier().equals("valueOf") && miArg.arguments().size() == 2) {
|
||||
innerArg = (org.eclipse.jdt.core.dom.Expression) miArg.arguments().get(1);
|
||||
}
|
||||
current = innerArg.toString();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
@@ -8,4 +8,8 @@ import java.util.List;
|
||||
|
||||
public interface CallGraphEngine {
|
||||
List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers);
|
||||
|
||||
default java.util.Map<String, java.util.List<click.kamil.springstatemachineexporter.analysis.model.CallEdge>> getCallGraph() {
|
||||
return java.util.Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@ package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class CallGraphPathFinder {
|
||||
private final CodebaseContext context;
|
||||
private final Map<java.util.Map.Entry<String, String>, Boolean> heuristicCache = new HashMap<>();
|
||||
|
||||
private final Map<java.util.Map.Entry<String, String>, List<List<String>>> memoCache = new HashMap<>();
|
||||
private final Map<java.util.Map.Entry<String, String>, Boolean> unreachableCache = new HashMap<>();
|
||||
|
||||
public CallGraphPathFinder(CodebaseContext context) {
|
||||
this.context = context;
|
||||
@@ -47,6 +52,129 @@ public class CallGraphPathFinder {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
||||
return findAllPaths(start, target, graph, visited, new boolean[]{false});
|
||||
}
|
||||
|
||||
public List<List<String>> findAllPaths(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited, boolean[] parentHitCycle) {
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
java.util.Map.Entry<String, String> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(start, target);
|
||||
if (unreachableCache.containsKey(cacheKey)) {
|
||||
return result;
|
||||
}
|
||||
if (memoCache.containsKey(cacheKey)) {
|
||||
List<List<String>> cached = memoCache.get(cacheKey);
|
||||
for (List<String> path : cached) {
|
||||
boolean hasCycle = false;
|
||||
for (String node : path) {
|
||||
if (visited.contains(node)) {
|
||||
hasCycle = true;
|
||||
parentHitCycle[0] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasCycle) {
|
||||
result.add(new ArrayList<>(path));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (start.equals(target)) {
|
||||
result.add(new ArrayList<>(List.of(start)));
|
||||
return result;
|
||||
}
|
||||
if (!visited.add(start)) {
|
||||
parentHitCycle[0] = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
boolean[] localHitCycle = new boolean[]{false};
|
||||
|
||||
List<CallEdge> neighbors = graph.get(start);
|
||||
if (neighbors != null) {
|
||||
for (CallEdge edge : neighbors) {
|
||||
String neighbor = edge.getTargetMethod();
|
||||
if (neighbor.startsWith("java.") || neighbor.startsWith("javax.") || neighbor.startsWith("jakarta.") || neighbor.startsWith("org.springframework.")) continue;
|
||||
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
|
||||
List<String> path = new ArrayList<>(List.of(start, target));
|
||||
result.add(path);
|
||||
} else {
|
||||
List<List<String>> subPaths = findAllPaths(neighbor, target, graph, visited, localHitCycle);
|
||||
for (List<String> subPath : subPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
path.addAll(subPath);
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle inheritance fallback for concrete class methods not in the graph
|
||||
if (result.isEmpty() && start.contains(".")) {
|
||||
String className = start.substring(0, start.lastIndexOf('.'));
|
||||
String methodName = start.substring(start.lastIndexOf('.') + 1);
|
||||
if (className.contains("<")) {
|
||||
className = className.substring(0, className.indexOf('<'));
|
||||
}
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td != null) {
|
||||
String superclass = context.getSuperclassFqn(td);
|
||||
if (superclass != null) {
|
||||
String superMethod = superclass + "." + methodName;
|
||||
if (graph.containsKey(superMethod)) {
|
||||
List<List<String>> superPaths = findAllPaths(superMethod, target, graph, visited, localHitCycle);
|
||||
for (List<String> superPath : superPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
path.addAll(superPath);
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle implementation fallback for interfaces/superclasses (interface -> concrete class)
|
||||
if (result.isEmpty() && start.contains(".")) {
|
||||
String className = start.substring(0, start.lastIndexOf('.'));
|
||||
String methodName = start.substring(start.lastIndexOf('.') + 1);
|
||||
if (className.contains("<")) {
|
||||
className = className.substring(0, className.indexOf('<'));
|
||||
}
|
||||
List<String> impls = context.getImplementations(className);
|
||||
if (impls != null) {
|
||||
for (String impl : impls) {
|
||||
String implMethod = impl + "." + methodName;
|
||||
if (graph.containsKey(implMethod)) {
|
||||
List<List<String>> implPaths = findAllPaths(implMethod, target, graph, visited, localHitCycle);
|
||||
for (List<String> implPath : implPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
path.addAll(implPath);
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.remove(start);
|
||||
|
||||
if (localHitCycle[0]) {
|
||||
parentHitCycle[0] = true;
|
||||
} else if (result.isEmpty()) {
|
||||
unreachableCache.put(cacheKey, true);
|
||||
} else {
|
||||
List<List<String>> toCache = new ArrayList<>();
|
||||
for (List<String> path : result) {
|
||||
toCache.add(new ArrayList<>(path));
|
||||
}
|
||||
memoCache.put(cacheKey, toCache);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
|
||||
for (String node : path) {
|
||||
List<CallEdge> edges = callGraph.get(node);
|
||||
@@ -68,6 +196,18 @@ public class CallGraphPathFinder {
|
||||
if (neighbor == null || target == null) return false;
|
||||
if (neighbor.equals(target)) return true;
|
||||
|
||||
java.util.Map.Entry<String, String> cacheKey = new java.util.AbstractMap.SimpleImmutableEntry<>(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) {
|
||||
if (neighbor.equals(target)) return true;
|
||||
|
||||
if (isFullyQualifiedMethod(neighbor) && isFullyQualifiedMethod(target)) {
|
||||
int lastDotNeighbor = neighbor.lastIndexOf('.');
|
||||
int lastDotTarget = target.lastIndexOf('.');
|
||||
@@ -89,7 +229,6 @@ public class CallGraphPathFinder {
|
||||
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||
|
||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||
if (simpleClassNeighbor.contains(simpleClassTarget) || simpleClassTarget.contains(simpleClassNeighbor)) return true;
|
||||
|
||||
List<String> impls = context.getImplementations(classTarget);
|
||||
if (impls != null) {
|
||||
@@ -109,7 +248,21 @@ public class CallGraphPathFinder {
|
||||
|
||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CompositeCallGraphEngine implements CallGraphEngine {
|
||||
private final List<CallGraphEngine> engines;
|
||||
private final Map<String, List<CallEdge>> mergedGraph = new HashMap<>();
|
||||
|
||||
public CompositeCallGraphEngine(List<CallGraphEngine> engines) {
|
||||
this.engines = engines;
|
||||
mergeGraphs();
|
||||
}
|
||||
|
||||
private void mergeGraphs() {
|
||||
for (CallGraphEngine engine : engines) {
|
||||
Map<String, List<CallEdge>> graph = engine.getCallGraph();
|
||||
if (graph != null) {
|
||||
for (Map.Entry<String, List<CallEdge>> entry : graph.entrySet()) {
|
||||
String caller = normalizeCompanionFqn(entry.getKey());
|
||||
List<CallEdge> edges = mergedGraph.computeIfAbsent(caller, k -> new ArrayList<>());
|
||||
for (CallEdge edge : entry.getValue()) {
|
||||
String target = normalizeCompanionFqn(edge.getTargetMethod());
|
||||
edges.add(new CallEdge(target, edge.getArguments(), edge.getReceiver()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeCompanionFqn(String fqn) {
|
||||
if (fqn == null) return null;
|
||||
return fqn.replace(".Companion.", ".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<CallEdge>> getCallGraph() {
|
||||
return mergedGraph;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
|
||||
List<CallChain> chains = new ArrayList<>();
|
||||
for (EntryPoint ep : entryPoints) {
|
||||
String startMethod = normalizeCompanionFqn(ep.getClassName() + "." + ep.getMethodName());
|
||||
for (TriggerPoint tp : triggers) {
|
||||
String targetMethod = normalizeCompanionFqn(tp.getClassName() + "." + tp.getMethodName());
|
||||
List<List<String>> paths = findAllPaths(startMethod, targetMethod, new LinkedHashSet<>());
|
||||
for (List<String> path : paths) {
|
||||
chains.add(CallChain.builder()
|
||||
.entryPoint(ep)
|
||||
.methodChain(path)
|
||||
.triggerPoint(tp)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
return chains;
|
||||
}
|
||||
|
||||
private List<List<String>> findAllPaths(String start, String target, Set<String> visited) {
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
if (start.equals(target)) {
|
||||
result.add(new ArrayList<>(List.of(start)));
|
||||
return result;
|
||||
}
|
||||
if (!visited.add(start)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
List<CallEdge> neighbors = mergedGraph.get(start);
|
||||
if (neighbors != null) {
|
||||
for (CallEdge edge : neighbors) {
|
||||
String neighbor = edge.getTargetMethod();
|
||||
if (neighbor.startsWith("java.") || neighbor.startsWith("javax.") ||
|
||||
neighbor.startsWith("jakarta.") || neighbor.startsWith("org.springframework.")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
|
||||
List<String> path = new ArrayList<>(List.of(start, target));
|
||||
result.add(path);
|
||||
} else {
|
||||
List<List<String>> subPaths = findAllPaths(neighbor, target, visited);
|
||||
for (List<String> subPath : subPaths) {
|
||||
List<String> path = new ArrayList<>();
|
||||
path.add(start);
|
||||
path.addAll(subPath);
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.remove(start);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isHeuristicMatch(String neighbor, String target) {
|
||||
if (neighbor == null || target == null) return false;
|
||||
if (neighbor.equals(target)) return true;
|
||||
if (neighbor.contains(".") && target.contains(".")) {
|
||||
String methodNeighbor = neighbor.substring(neighbor.lastIndexOf('.') + 1);
|
||||
String methodTarget = target.substring(target.lastIndexOf('.') + 1);
|
||||
if (methodNeighbor.equals(methodTarget)) {
|
||||
String classNeighbor = neighbor.substring(0, neighbor.lastIndexOf('.'));
|
||||
String classTarget = target.substring(0, target.lastIndexOf('.'));
|
||||
String simpleClassNeighbor = classNeighbor.contains(".") ? classNeighbor.substring(classNeighbor.lastIndexOf('.') + 1) : classNeighbor;
|
||||
String simpleClassTarget = classTarget.contains(".") ? classTarget.substring(classTarget.lastIndexOf('.') + 1) : classTarget;
|
||||
return simpleClassNeighbor.equalsIgnoreCase(simpleClassTarget);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class CompositeIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
private final List<CodebaseIntelligenceProvider> providers;
|
||||
private final List<CallGraphEngine> engines;
|
||||
|
||||
public CompositeIntelligenceProvider(List<CodebaseIntelligenceProvider> providers, List<CallGraphEngine> engines) {
|
||||
this.providers = providers;
|
||||
this.engines = engines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TriggerPoint> findTriggerPoints() {
|
||||
List<TriggerPoint> result = new ArrayList<>();
|
||||
for (CodebaseIntelligenceProvider provider : providers) {
|
||||
result.addAll(provider.findTriggerPoints());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntryPoint> findEntryPoints() {
|
||||
List<EntryPoint> result = new ArrayList<>();
|
||||
for (CodebaseIntelligenceProvider provider : providers) {
|
||||
result.addAll(provider.findEntryPoints());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CallChain> findCallChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
|
||||
CompositeCallGraphEngine compositeEngine = new CompositeCallGraphEngine(engines);
|
||||
return compositeEngine.findChains(entryPoints, triggers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, String>> resolveProperties() {
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
for (CodebaseIntelligenceProvider provider : providers) {
|
||||
Map<String, Map<String, String>> props = provider.resolveProperties();
|
||||
if (props != null) {
|
||||
result.putAll(props);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,21 @@ public class ConstantExtractor {
|
||||
}
|
||||
|
||||
public void extractConstantsFromExpression(Expression expr, List<String> constants) {
|
||||
if (constantResolver != null) {
|
||||
String resolved = constantResolver.resolve(expr, context);
|
||||
if (resolved != null) {
|
||||
if (resolved.startsWith("ENUM_SET:")) {
|
||||
for (String eVal : resolved.substring(9).split(",")) {
|
||||
String parsed = parseEnumSetElement(eVal);
|
||||
if (!constants.contains(parsed)) constants.add(parsed);
|
||||
}
|
||||
} else {
|
||||
constants.add(resolved);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (expr instanceof QualifiedName qn) {
|
||||
constants.add(qn.toString());
|
||||
} else if (expr instanceof StringLiteral sl) {
|
||||
@@ -74,7 +89,8 @@ public class ConstantExtractor {
|
||||
} else if (expr instanceof MethodInvocation mi) {
|
||||
String methodName = mi.getName().getIdentifier();
|
||||
|
||||
System.out.println("Processing mi: " + mi); if (methodName.equals("get") || methodName.equals("getOrDefault")) {
|
||||
log.trace("Processing mi: {}", mi);
|
||||
if (methodName.equals("get") || methodName.equals("getOrDefault")) {
|
||||
if (mi.getExpression() != null) {
|
||||
if (variableTracer != null) {
|
||||
List<Expression> traced = variableTracer.traceVariableAll(mi.getExpression());
|
||||
@@ -266,6 +282,7 @@ public class ConstantExtractor {
|
||||
}
|
||||
|
||||
public List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
||||
log.debug("ConstantExtractor.resolveMethodReturnConstant CALLED: className={}, methodName={}", className, methodName);
|
||||
log.debug("Entering resolveMethodReturnConstant with className={}, methodName={}, depth={}", className, methodName, depth);
|
||||
if (depth > 20) {
|
||||
log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName);
|
||||
@@ -333,6 +350,9 @@ public class ConstantExtractor {
|
||||
}
|
||||
if (!handled && variableTracer != null && constructorAnalyzer != null) {
|
||||
List<Expression> tracedRetExprs = variableTracer.traceVariableAll(retExpr);
|
||||
if (tracedRetExprs.isEmpty() && retExpr != null) {
|
||||
tracedRetExprs = List.of(retExpr);
|
||||
}
|
||||
for (Expression tracedRetExpr : tracedRetExprs) {
|
||||
extractConstantsFromExpression(tracedRetExpr, constants);
|
||||
String val = constantResolver.resolve(tracedRetExpr, context);
|
||||
@@ -373,6 +393,7 @@ public class ConstantExtractor {
|
||||
}
|
||||
}
|
||||
visited.remove(fqn);
|
||||
log.debug("resolveMethodReturnConstant className={} methodName={} returns: {}", className, methodName, constants);
|
||||
log.debug("resolveMethodReturnConstant for class={}, method={} resolved constants: {}", className, methodName, constants);
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ConstructorAnalyzer {
|
||||
private final CodebaseContext context;
|
||||
private final ConstantResolver constantResolver;
|
||||
@@ -245,7 +248,8 @@ public class ConstructorAnalyzer {
|
||||
boolean matches = false;
|
||||
if (resolvedCtor != null && ctor.resolveBinding() != null) {
|
||||
matches = resolvedCtor.isEqualTo(ctor.resolveBinding()) || resolvedCtor.getKey().equals(ctor.resolveBinding().getKey());
|
||||
} else {
|
||||
}
|
||||
if (!matches) {
|
||||
matches = ctor.parameters().size() == cic.arguments().size();
|
||||
}
|
||||
if (!matches) continue;
|
||||
@@ -271,39 +275,19 @@ public class ConstructorAnalyzer {
|
||||
if (resolved != null) paramValues.put(pName, resolved);
|
||||
}
|
||||
}
|
||||
if (paramValues.isEmpty()) continue;
|
||||
// Evaluate constructor body regardless of whether parameters could be resolved as constants
|
||||
|
||||
ctor.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(Assignment node) {
|
||||
Expression lhs = node.getLeftHandSide();
|
||||
Expression rhs = node.getRightHandSide();
|
||||
|
||||
String fieldName = null;
|
||||
if (lhs instanceof FieldAccess fa
|
||||
&& fa.getExpression() instanceof ThisExpression) {
|
||||
fieldName = fa.getName().getIdentifier();
|
||||
} else if (lhs instanceof SimpleName sn
|
||||
&& !paramValues.containsKey(sn.getIdentifier())) {
|
||||
fieldName = sn.getIdentifier();
|
||||
}
|
||||
|
||||
if (fieldName != null) {
|
||||
String paramVal = null;
|
||||
if (rhs instanceof SimpleName snRhs) {
|
||||
paramVal = paramValues.get(snRhs.getIdentifier());
|
||||
} else if (rhsResolver != null) {
|
||||
paramVal = rhsResolver.resolve(rhs, paramValues, td);
|
||||
}
|
||||
|
||||
if (paramVal != null) {
|
||||
fieldValues.put(fieldName, paramVal);
|
||||
fieldValues.put("this." + fieldName, paramVal);
|
||||
java.util.Map<String, String> localVars = new java.util.HashMap<>(paramValues);
|
||||
constantResolver.evaluateMethodBodyWithLocals(ctor, localVars, context, new java.util.HashSet<>());
|
||||
for (java.util.Map.Entry<String, String> entry : localVars.entrySet()) {
|
||||
if (entry.getKey().startsWith("this.")) {
|
||||
fieldValues.put(entry.getKey(), entry.getValue());
|
||||
fieldValues.put(entry.getKey().substring(5), entry.getValue());
|
||||
} else if (context.hasField(td, entry.getKey())) {
|
||||
fieldValues.put(entry.getKey(), entry.getValue());
|
||||
fieldValues.put("this." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
|
||||
ctor.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
@@ -383,43 +367,29 @@ public class ConstructorAnalyzer {
|
||||
resolved = constantResolver.resolve(argExpr, context);
|
||||
}
|
||||
}
|
||||
if (resolved == null && rhsResolver != null) {
|
||||
TypeDeclaration enclosingTd = findEnclosingType(argExpr);
|
||||
if (enclosingTd != null) {
|
||||
resolved = rhsResolver.resolve(argExpr, subClassParamValues, enclosingTd);
|
||||
}
|
||||
}
|
||||
if (resolved != null) {
|
||||
superParamValues.put(pName, resolved);
|
||||
}
|
||||
}
|
||||
if (superParamValues.isEmpty()) continue;
|
||||
|
||||
superCtor.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(Assignment node) {
|
||||
Expression lhs = node.getLeftHandSide();
|
||||
Expression rhs = node.getRightHandSide();
|
||||
|
||||
String fieldName = null;
|
||||
if (lhs instanceof FieldAccess fa
|
||||
&& fa.getExpression() instanceof ThisExpression) {
|
||||
fieldName = fa.getName().getIdentifier();
|
||||
} else if (lhs instanceof SimpleName sn
|
||||
&& !superParamValues.containsKey(sn.getIdentifier())) {
|
||||
fieldName = sn.getIdentifier();
|
||||
}
|
||||
|
||||
if (fieldName != null) {
|
||||
String paramVal = null;
|
||||
if (rhs instanceof SimpleName snRhs) {
|
||||
paramVal = superParamValues.get(snRhs.getIdentifier());
|
||||
} else if (rhsResolver != null) {
|
||||
paramVal = rhsResolver.resolve(rhs, superParamValues, superTd);
|
||||
}
|
||||
|
||||
if (paramVal != null) {
|
||||
fieldValues.put(fieldName, paramVal);
|
||||
fieldValues.put("this." + fieldName, paramVal);
|
||||
java.util.Map<String, String> localVars = new java.util.HashMap<>(superParamValues);
|
||||
constantResolver.evaluateMethodBodyWithLocals(superCtor, localVars, context, new java.util.HashSet<>());
|
||||
for (java.util.Map.Entry<String, String> entry : localVars.entrySet()) {
|
||||
if (entry.getKey().startsWith("this.")) {
|
||||
fieldValues.put(entry.getKey(), entry.getValue());
|
||||
fieldValues.put(entry.getKey().substring(5), entry.getValue());
|
||||
} else if (context.hasField(superTd, entry.getKey())) {
|
||||
fieldValues.put(entry.getKey(), entry.getValue());
|
||||
fieldValues.put("this." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
|
||||
superCtor.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
@@ -472,6 +442,13 @@ public class ConstructorAnalyzer {
|
||||
}
|
||||
|
||||
MethodDeclaration method = context.findMethodDeclaration(td, methodName, true);
|
||||
if (method == null) {
|
||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||
TypeDeclaration targetTd = context.resolveStaticImport(methodName, cu);
|
||||
if (targetTd != null) {
|
||||
method = context.findMethodDeclaration(targetTd, methodName, true);
|
||||
}
|
||||
}
|
||||
if (method == null || method.getBody() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -576,10 +553,12 @@ public class ConstructorAnalyzer {
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, String> fieldValues = buildFieldValuesFromCIC(td, cic, context);
|
||||
Map<String, String> fieldValues = buildFieldValuesFromCIC(td, cic, context, this::evaluateMethodCallInConstructor);
|
||||
log.debug("RESOLVE_GETTER: td={}, fieldValues={}", context.getFqn(td), fieldValues);
|
||||
if (fieldValues.isEmpty()) return Collections.emptyList();
|
||||
|
||||
String result = constantResolver.evaluateMethodBodyWithLocals(getter, fieldValues, context, visited);
|
||||
log.debug("RESOLVE_GETTER: result={}", result);
|
||||
return result != null ? List.of(result) : Collections.emptyList();
|
||||
} finally {
|
||||
visited.remove(getterKey);
|
||||
@@ -665,6 +644,12 @@ public class ConstructorAnalyzer {
|
||||
if (targetIdx >= 0 && targetIdx < arguments.size()) {
|
||||
Expression arg = (Expression) arguments.get(targetIdx);
|
||||
String val = constantResolver.resolve(arg, context);
|
||||
if (val == null && arg instanceof MethodInvocation mi) {
|
||||
TypeDeclaration enclosingTd = findEnclosingType(arg);
|
||||
if (enclosingTd != null) {
|
||||
val = evaluateMethodCallInConstructor(arg, new HashMap<>(), enclosingTd);
|
||||
}
|
||||
}
|
||||
if (val != null) {
|
||||
if (val.startsWith("ENUM_SET:")) {
|
||||
for (String eVal : val.substring(9).split(",")) {
|
||||
|
||||
@@ -34,9 +34,7 @@ public class DynamicClasspathResolver {
|
||||
|
||||
protected List<String> resolveMaven(Path projectRoot) {
|
||||
log.info("Maven project detected. Resolving dependencies...");
|
||||
Path cpFile = null;
|
||||
try {
|
||||
cpFile = Files.createTempFile("state_machine_exporter_cp", ".txt");
|
||||
String mvnCmd = getCommand(projectRoot, "mvnw", "mvn");
|
||||
if (mvnCmd == null) {
|
||||
log.warn("Maven executable not found.");
|
||||
@@ -48,26 +46,32 @@ public class DynamicClasspathResolver {
|
||||
"-q",
|
||||
"dependency:build-classpath",
|
||||
"-DincludeScope=compile",
|
||||
"-Dmdep.outputFile=" + cpFile.toAbsolutePath().toString()
|
||||
"-Dmdep.outputFile=sme_cp.txt"
|
||||
);
|
||||
pb.directory(projectRoot.toFile());
|
||||
|
||||
runProcess(pb);
|
||||
|
||||
if (Files.exists(cpFile)) {
|
||||
java.util.Set<String> allPaths = new java.util.LinkedHashSet<>();
|
||||
try (java.util.stream.Stream<Path> stream = Files.walk(projectRoot)) {
|
||||
stream.filter(p -> p.getFileName().toString().equals("sme_cp.txt"))
|
||||
.forEach(cpFile -> {
|
||||
try {
|
||||
String cpString = Files.readString(cpFile).trim();
|
||||
if (!cpString.isEmpty()) {
|
||||
return Arrays.asList(cpString.split(File.pathSeparator));
|
||||
allPaths.addAll(Arrays.asList(cpString.split(File.pathSeparator)));
|
||||
}
|
||||
Files.deleteIfExists(cpFile);
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to read or delete sme_cp.txt at {}", cpFile, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!allPaths.isEmpty()) {
|
||||
return new ArrayList<>(allPaths);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to dynamically resolve Maven classpath", e);
|
||||
} finally {
|
||||
if (cpFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(cpFile);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -107,14 +111,18 @@ public class DynamicClasspathResolver {
|
||||
pb.directory(projectRoot.toFile());
|
||||
|
||||
String output = runProcessAndGetOutput(pb);
|
||||
java.util.Set<String> allPaths = new java.util.LinkedHashSet<>();
|
||||
for (String line : output.split("\\r?\\n")) {
|
||||
if (line.startsWith("SME_CLASSPATH_MARKER:")) {
|
||||
String cpString = line.substring("SME_CLASSPATH_MARKER:".length()).trim();
|
||||
if (!cpString.isEmpty()) {
|
||||
return Arrays.asList(cpString.split(File.pathSeparator));
|
||||
allPaths.addAll(Arrays.asList(cpString.split(File.pathSeparator)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!allPaths.isEmpty()) {
|
||||
return new ArrayList<>(allPaths);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to dynamically resolve Gradle classpath", e);
|
||||
} finally {
|
||||
@@ -143,11 +151,14 @@ public class DynamicClasspathResolver {
|
||||
}
|
||||
|
||||
protected void runProcess(ProcessBuilder pb) throws IOException, InterruptedException {
|
||||
pb.redirectErrorStream(true);
|
||||
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
|
||||
Process p = pb.start();
|
||||
p.waitFor();
|
||||
}
|
||||
|
||||
protected String runProcessAndGetOutput(ProcessBuilder pb) throws IOException, InterruptedException {
|
||||
pb.redirectErrorStream(true);
|
||||
Process p = pb.start();
|
||||
StringBuilder out = new StringBuilder();
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
|
||||
|
||||
@@ -70,7 +70,14 @@ public class GenericEventDetector {
|
||||
String sourceState = extractSourceState(node);
|
||||
String[] smTypes = resolveStateMachineTypeArgumentsForExpression(emr.getExpression(), node);
|
||||
|
||||
boolean external = false;
|
||||
if (receiver != null) {
|
||||
AssignmentDag dag = new AssignmentDag(method);
|
||||
external = dag.isExternal(receiver, new java.util.HashSet<>());
|
||||
}
|
||||
|
||||
if (eventValue != null) {
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
triggers.add(TriggerPoint.builder()
|
||||
.event(eventValue)
|
||||
.sourceState(sourceState)
|
||||
@@ -80,6 +87,8 @@ public class GenericEventDetector {
|
||||
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
||||
.stateTypeFqn(smTypes[0])
|
||||
.eventTypeFqn(smTypes[1])
|
||||
.external(external)
|
||||
.constraint(constraint)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -228,6 +237,15 @@ public class GenericEventDetector {
|
||||
String sourceState = extractSourceState(node);
|
||||
String[] smTypes = resolveStateMachineTypeArguments(node);
|
||||
|
||||
boolean external = false;
|
||||
if (!node.arguments().isEmpty()) {
|
||||
Expression eventExpr = (Expression) node.arguments().get(0);
|
||||
AssignmentDag dag = new AssignmentDag(method);
|
||||
external = dag.isExternal(eventExpr, new java.util.HashSet<>());
|
||||
}
|
||||
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
|
||||
List<TriggerPoint> results = new ArrayList<>();
|
||||
if (eventValue != null && eventValue.startsWith("ENUM_SET:")) {
|
||||
String[] parts = eventValue.substring(9).split(",");
|
||||
@@ -242,6 +260,8 @@ public class GenericEventDetector {
|
||||
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
||||
.stateTypeFqn(smTypes[0])
|
||||
.eventTypeFqn(smTypes[1])
|
||||
.external(external)
|
||||
.constraint(constraint)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -255,6 +275,8 @@ public class GenericEventDetector {
|
||||
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
||||
.stateTypeFqn(smTypes[0])
|
||||
.eventTypeFqn(smTypes[1])
|
||||
.external(external)
|
||||
.constraint(constraint)
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -503,6 +525,21 @@ public class GenericEventDetector {
|
||||
|
||||
private String[] resolveStateMachineTypeArguments(MethodInvocation node) {
|
||||
Expression receiver = node.getExpression();
|
||||
|
||||
// Trace back the receiver if it's a builder chain
|
||||
while (receiver instanceof MethodInvocation mi) {
|
||||
if (mi.getName().getIdentifier().equals("sendEvent")) {
|
||||
receiver = mi.getExpression();
|
||||
break;
|
||||
}
|
||||
receiver = mi.getExpression();
|
||||
}
|
||||
|
||||
// Also if the node itself is sendEvent, receiver is already correct
|
||||
if (node.getName().getIdentifier().equals("sendEvent")) {
|
||||
receiver = node.getExpression();
|
||||
}
|
||||
|
||||
if (receiver == null) return new String[]{null, null};
|
||||
|
||||
ITypeBinding binding = receiver.resolveTypeBinding();
|
||||
@@ -524,13 +561,57 @@ public class GenericEventDetector {
|
||||
CompilationUnit cu = (CompilationUnit) node.getRoot();
|
||||
String stateType = resolveTypeToFqn((Type) typeArgs.get(0), cu);
|
||||
String eventType = resolveTypeToFqn((Type) typeArgs.get(1), cu);
|
||||
|
||||
if (stateType != null && stateType.length() == 1) {
|
||||
stateType = resolveGenericTypeVariable(stateType, node, cu);
|
||||
}
|
||||
if (eventType != null && eventType.length() == 1) {
|
||||
eventType = resolveGenericTypeVariable(eventType, node, cu);
|
||||
}
|
||||
|
||||
return new String[]{stateType, eventType};
|
||||
}
|
||||
}
|
||||
|
||||
return new String[]{null, null};
|
||||
}
|
||||
|
||||
private String resolveGenericTypeVariable(String typeVarName, ASTNode node, CompilationUnit cu) {
|
||||
TypeDeclaration enclosingClass = findEnclosingType(node);
|
||||
if (enclosingClass == null) return typeVarName;
|
||||
String className = context.getFqn(enclosingClass);
|
||||
if (className == null) return typeVarName;
|
||||
|
||||
List<String> impls = context.getImplementations(className);
|
||||
if (impls == null || impls.isEmpty()) return typeVarName;
|
||||
|
||||
for (String implFqn : impls) {
|
||||
org.eclipse.jdt.core.dom.AbstractTypeDeclaration implNode = context.getAbstractTypeDeclaration(implFqn);
|
||||
if (implNode instanceof TypeDeclaration td) {
|
||||
Type superclassType = td.getSuperclassType();
|
||||
if (superclassType instanceof ParameterizedType pt) {
|
||||
Type baseType = pt.getType();
|
||||
String baseName = resolveTypeToFqn(baseType, (CompilationUnit) implNode.getRoot());
|
||||
if (className.equals(baseName) || className.endsWith("." + baseName)) {
|
||||
int typeIndex = -1;
|
||||
List<?> typeParams = enclosingClass.typeParameters();
|
||||
for (int i = 0; i < typeParams.size(); i++) {
|
||||
TypeParameter tp = (TypeParameter) typeParams.get(i);
|
||||
if (tp.getName().getIdentifier().equals(typeVarName)) {
|
||||
typeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (typeIndex >= 0 && typeIndex < pt.typeArguments().size()) {
|
||||
Type concreteType = (Type) pt.typeArguments().get(typeIndex);
|
||||
return resolveTypeToFqn(concreteType, (CompilationUnit) implNode.getRoot());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return typeVarName;
|
||||
}
|
||||
|
||||
private ITypeBinding findStateMachineSupertype(ITypeBinding binding, java.util.Set<String> visited) {
|
||||
ITypeBinding current = binding;
|
||||
while (current != null) {
|
||||
@@ -635,4 +716,93 @@ public class GenericEventDetector {
|
||||
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
private static class AssignmentDag {
|
||||
final java.util.Map<String, List<Expression>> assignments = new java.util.HashMap<>();
|
||||
final java.util.Set<SingleVariableDeclaration> parameters = new java.util.HashSet<>();
|
||||
|
||||
AssignmentDag(MethodDeclaration method) {
|
||||
if (method == null) return;
|
||||
for (Object paramObj : method.parameters()) {
|
||||
if (paramObj instanceof SingleVariableDeclaration svd) {
|
||||
parameters.add(svd);
|
||||
}
|
||||
}
|
||||
if (method.getBody() != null) {
|
||||
method.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(VariableDeclarationFragment fragment) {
|
||||
String varName = fragment.getName().getIdentifier();
|
||||
if (fragment.getInitializer() != null) {
|
||||
assignments.computeIfAbsent(varName, k -> new ArrayList<>()).add(fragment.getInitializer());
|
||||
}
|
||||
return super.visit(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(Assignment assignment) {
|
||||
if (assignment.getLeftHandSide() instanceof SimpleName sn) {
|
||||
String varName = sn.getIdentifier();
|
||||
assignments.computeIfAbsent(varName, k -> new ArrayList<>()).add(assignment.getRightHandSide());
|
||||
}
|
||||
return super.visit(assignment);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
boolean isExternal(Expression expr, java.util.Set<String> visited) {
|
||||
if (expr == null) return false;
|
||||
Expression peeled = expr;
|
||||
while (peeled instanceof ParenthesizedExpression pe) {
|
||||
peeled = pe.getExpression();
|
||||
}
|
||||
while (peeled instanceof CastExpression ce) {
|
||||
peeled = ce.getExpression();
|
||||
}
|
||||
|
||||
if (peeled instanceof SimpleName sn) {
|
||||
String varName = sn.getIdentifier();
|
||||
for (SingleVariableDeclaration svd : parameters) {
|
||||
if (svd.getName().getIdentifier().equals(varName)) {
|
||||
return isAnnotatedAsExternal(svd);
|
||||
}
|
||||
}
|
||||
if (assignments.containsKey(varName)) {
|
||||
if (!visited.add(varName)) {
|
||||
return false;
|
||||
}
|
||||
List<Expression> defs = assignments.get(varName);
|
||||
for (Expression def : defs) {
|
||||
if (isExternal(def, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (peeled instanceof MethodInvocation mi) {
|
||||
if (mi.getExpression() != null && isExternal(mi.getExpression(), visited)) {
|
||||
return true;
|
||||
}
|
||||
for (Object arg : mi.arguments()) {
|
||||
if (isExternal((Expression) arg, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAnnotatedAsExternal(SingleVariableDeclaration svd) {
|
||||
for (Object modifier : svd.modifiers()) {
|
||||
if (modifier instanceof Annotation annotation) {
|
||||
String typeName = annotation.getTypeName().getFullyQualifiedName();
|
||||
if (typeName.endsWith("PathVariable") || typeName.endsWith("RequestBody") || typeName.endsWith("RequestParam")
|
||||
|| typeName.endsWith("PathParam") || typeName.endsWith("QueryParam")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,13 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, List<CallEdge>> buildCallGraph() {
|
||||
Map<String, List<CallEdge>> cached = (Map<String, List<CallEdge>>) context.getCache().get("heuristicCallGraph");
|
||||
if (cached != null) {
|
||||
this.graph = cached;
|
||||
return cached;
|
||||
}
|
||||
graph = new HashMap<>();
|
||||
for (CompilationUnit cu : context.getCompilationUnits()) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
@@ -34,9 +40,12 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
|
||||
List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
|
||||
List<String> args = resolveArguments(node.arguments());
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
for (String calledMethod : calledMethods) {
|
||||
String receiver = getReceiverString(node.getExpression());
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args, receiver));
|
||||
CallEdge edge = new CallEdge(calledMethod, args, receiver);
|
||||
edge.setConstraint(resolveConstraint(node, calledMethod, constraint));
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
for (Object argObj : node.arguments()) {
|
||||
if (argObj instanceof ExpressionMethodReference emr) {
|
||||
@@ -53,7 +62,9 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
implicitArgs.addAll(args);
|
||||
}
|
||||
String receiver = getReceiverString(node.getExpression());
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, implicitArgs, receiver));
|
||||
CallEdge edge = new CallEdge(refMethod, implicitArgs, receiver);
|
||||
edge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -73,11 +84,15 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
implicitArgs.addAll(args);
|
||||
}
|
||||
String receiver = getReceiverString(node.getExpression());
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, implicitArgs, receiver));
|
||||
CallEdge edge = new CallEdge(calledMethod, implicitArgs, receiver);
|
||||
edge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
|
||||
List<String> impls = context.getImplementations(fallbackTypeFqn);
|
||||
for (String impl : impls) {
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args, receiver));
|
||||
CallEdge implEdge = new CallEdge(impl + "." + emr.getName().getIdentifier(), args, receiver);
|
||||
implEdge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(implEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +125,10 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
List<String> args = resolveArguments(node.arguments());
|
||||
String receiver = "super";
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args, receiver));
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
CallEdge edge = new CallEdge(calledMethod, args, receiver);
|
||||
edge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +137,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
});
|
||||
}
|
||||
context.getCache().put("heuristicCallGraph", graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
@@ -148,33 +167,6 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
|
||||
return resolvedValue;
|
||||
}
|
||||
|
||||
protected List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
|
||||
String baseCalled = resolveCalledMethod(node);
|
||||
if (baseCalled == null) return Collections.emptyList();
|
||||
|
||||
List<String> allResolved = new ArrayList<>();
|
||||
allResolved.add(baseCalled);
|
||||
|
||||
if (baseCalled.contains(".")) {
|
||||
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
|
||||
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
|
||||
|
||||
String definingClass = findDefiningClass(className, methodName);
|
||||
if (definingClass != null && !definingClass.equals(className)) {
|
||||
allResolved.set(0, definingClass + "." + methodName);
|
||||
className = definingClass;
|
||||
}
|
||||
|
||||
List<String> impls = context.getImplementations(className);
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
}
|
||||
}
|
||||
|
||||
return allResolved;
|
||||
}
|
||||
|
||||
private String findDefiningClass(String className, String methodName) {
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td == null) return null;
|
||||
@@ -607,6 +599,14 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
|
||||
if (receiverType != null) {
|
||||
String rawType = receiverType;
|
||||
if (rawType.contains("<")) {
|
||||
rawType = rawType.substring(0, rawType.indexOf('<'));
|
||||
}
|
||||
if (rawType.equals("java.util.Map") || rawType.equals("Map")) {
|
||||
String valType = resolveMapValueType(mi);
|
||||
if (valType != null) return valType;
|
||||
}
|
||||
if (receiverType.contains("<")) {
|
||||
receiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||
}
|
||||
@@ -623,6 +623,12 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IMethodBinding methodBinding = mi.resolveMethodBinding();
|
||||
if (methodBinding != null && methodBinding.getReturnType() != null) {
|
||||
return methodBinding.getReturnType().getErasure().getQualifiedName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,18 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
this.injectionAnalyzer = injectionAnalyzer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<CallEdge>> getCallGraph() {
|
||||
return buildCallGraph();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, List<CallEdge>> buildCallGraph() {
|
||||
Map<String, List<CallEdge>> cached = (Map<String, List<CallEdge>>) context.getCache().get("jdtCallGraph");
|
||||
if (cached != null) {
|
||||
this.graph = cached;
|
||||
return cached;
|
||||
}
|
||||
graph = new HashMap<>();
|
||||
for (CompilationUnit cu : context.getCompilationUnits()) {
|
||||
cu.accept(new ASTVisitor() {
|
||||
@@ -38,8 +49,11 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
|
||||
List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
|
||||
List<String> args = resolveArguments(node.arguments());
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
for (String calledMethod : calledMethods) {
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
|
||||
CallEdge edge = new CallEdge(calledMethod, args);
|
||||
edge.setConstraint(resolveConstraint(node, calledMethod, constraint));
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
for (Object argObj : node.arguments()) {
|
||||
if (argObj instanceof ExpressionMethodReference emr) {
|
||||
@@ -55,7 +69,9 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
} else {
|
||||
implicitArgs.addAll(args);
|
||||
}
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, implicitArgs));
|
||||
CallEdge edge = new CallEdge(refMethod, implicitArgs);
|
||||
edge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -74,11 +90,15 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
} else {
|
||||
implicitArgs.addAll(args);
|
||||
}
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, implicitArgs));
|
||||
CallEdge edge = new CallEdge(calledMethod, implicitArgs);
|
||||
edge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
|
||||
List<String> impls = context.getImplementations(fallbackTypeFqn);
|
||||
for (String impl : impls) {
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args));
|
||||
CallEdge implEdge = new CallEdge(impl + "." + emr.getName().getIdentifier(), args);
|
||||
implEdge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(implEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +130,10 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
calledMethod = superFqn + "." + methodName;
|
||||
}
|
||||
List<String> args = resolveArguments(node.arguments());
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
|
||||
String constraint = click.kamil.springstatemachineexporter.ast.common.AstUtils.findConditionConstraint(node);
|
||||
CallEdge edge = new CallEdge(calledMethod, args);
|
||||
edge.setConstraint(constraint);
|
||||
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +142,7 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
});
|
||||
}
|
||||
context.getCache().put("jdtCallGraph", graph);
|
||||
return graph;
|
||||
}
|
||||
|
||||
@@ -143,27 +167,11 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
|
||||
// Delegate to base class for lambda, method reference, and constructor unwrapping
|
||||
return super.resolveArgument(expr);
|
||||
String result = super.resolveArgument(expr);
|
||||
if (result != null) {
|
||||
result = result.replaceAll("->\\s*yield\\s+", "-> ");
|
||||
}
|
||||
|
||||
private List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
|
||||
String baseCalled = resolveCalledMethod(node);
|
||||
if (baseCalled == null) return Collections.emptyList();
|
||||
|
||||
List<String> allResolved = new ArrayList<>();
|
||||
allResolved.add(baseCalled);
|
||||
|
||||
if (baseCalled.contains(".")) {
|
||||
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
|
||||
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
|
||||
|
||||
List<String> impls = context.getImplementations(className);
|
||||
for (String impl : impls) {
|
||||
allResolved.add(impl + "." + methodName);
|
||||
}
|
||||
}
|
||||
|
||||
return allResolved;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected String resolveCalledMethod(MethodInvocation node) {
|
||||
@@ -193,16 +201,16 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
nameBinding = fa.resolveFieldBinding();
|
||||
}
|
||||
if (nameBinding instanceof IVariableBinding varBinding) {
|
||||
System.out.println("CALLGRAPH RESOLVING BEAN FOR BINDING: " + varBinding.getName() + " calling " + methodName);
|
||||
log.debug("CALLGRAPH RESOLVING BEAN FOR BINDING: {} calling {}", varBinding.getName(), methodName);
|
||||
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
||||
if (concreteFqn != null) {
|
||||
System.out.println(" -> RESOLVED CONCRETE FQN: " + concreteFqn);
|
||||
log.debug(" -> RESOLVED CONCRETE FQN: {}", concreteFqn);
|
||||
return concreteFqn + "." + methodName;
|
||||
} else {
|
||||
System.out.println(" -> RESOLVE FAILED, RETURNED NULL");
|
||||
log.debug(" -> RESOLVE FAILED, RETURNED NULL");
|
||||
}
|
||||
} else {
|
||||
System.out.println("CALLGRAPH NAME BINDING IS NOT VARIABLE: " + nameBinding + " calling " + methodName);
|
||||
log.debug("CALLGRAPH NAME BINDING IS NOT VARIABLE: {} calling {}", nameBinding, methodName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,9 +239,12 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
|
||||
if (typeName != null && !typeName.isEmpty()) {
|
||||
org.eclipse.jdt.core.dom.TypeDeclaration resolvedTd = context.getTypeDeclaration(typeName);
|
||||
if (resolvedTd != null && context.findMethodDeclaration(resolvedTd, methodName, true) != null) {
|
||||
return typeName + "." + methodName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (receiver instanceof ThisExpression) {
|
||||
TypeDeclaration td = findEnclosingType(node);
|
||||
@@ -379,6 +390,14 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||
}
|
||||
|
||||
if (receiverType != null) {
|
||||
String rawType = receiverType;
|
||||
if (rawType.contains("<")) {
|
||||
rawType = rawType.substring(0, rawType.indexOf('<'));
|
||||
}
|
||||
if (rawType.equals("java.util.Map") || rawType.equals("Map")) {
|
||||
String valType = resolveMapValueType(mi);
|
||||
if (valType != null) return valType;
|
||||
}
|
||||
if (receiverType.contains("<")) {
|
||||
receiverType = receiverType.substring(0, receiverType.indexOf('<'));
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
private final LifecycleDetector lifecycleDetector;
|
||||
private final InterceptorDetector interceptorDetector;
|
||||
private final SpringComponentDetector componentDetector;
|
||||
private List<TriggerPoint> cachedTriggers;
|
||||
private List<EntryPoint> cachedEntryPoints;
|
||||
|
||||
public JdtIntelligenceProvider(CodebaseContext context, Path rootDir) {
|
||||
this.context = context;
|
||||
@@ -54,6 +56,9 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
|
||||
@Override
|
||||
public List<TriggerPoint> findTriggerPoints() {
|
||||
if (cachedTriggers != null) {
|
||||
return cachedTriggers;
|
||||
}
|
||||
List<TriggerPoint> allTriggers = new ArrayList<>();
|
||||
var cus = context.getCompilationUnits();
|
||||
log.info("JdtIntelligenceProvider scanning {} compilation units for triggers", cus.size());
|
||||
@@ -62,11 +67,15 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
allTriggers.addAll(lifecycleDetector.detect(cu));
|
||||
}
|
||||
log.info("Found {} triggers (including lifecycle) in total", allTriggers.size());
|
||||
cachedTriggers = allTriggers;
|
||||
return allTriggers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EntryPoint> findEntryPoints() {
|
||||
if (cachedEntryPoints != null) {
|
||||
return cachedEntryPoints;
|
||||
}
|
||||
List<EntryPoint> allEntryPoints = new ArrayList<>();
|
||||
var cus = context.getCompilationUnits();
|
||||
log.info("JdtIntelligenceProvider scanning {} compilation units for entry points", cus.size());
|
||||
@@ -77,6 +86,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
|
||||
allEntryPoints.addAll(componentDetector.detect(cu));
|
||||
}
|
||||
log.info("Found {} entry points (including interceptors and listeners) in total", allEntryPoints.size());
|
||||
cachedEntryPoints = allEntryPoints;
|
||||
return allEntryPoints;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,21 @@ public class TypeResolver {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String getParameterName(String methodFqn, int index) {
|
||||
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td != null) {
|
||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||
if (md != null && index < md.parameters().size()) {
|
||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(index);
|
||||
return svd.getName().getIdentifier();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getParameterType(String methodFqn, int index) {
|
||||
if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null;
|
||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||
|
||||
@@ -28,6 +28,16 @@ public class VariableTracer {
|
||||
return all.isEmpty() ? expr : all.get(all.size() - 1);
|
||||
}
|
||||
|
||||
private Expression peelExpression(Expression expr) {
|
||||
while (expr instanceof ParenthesizedExpression pe) {
|
||||
expr = pe.getExpression();
|
||||
}
|
||||
if (expr instanceof CastExpression ce) {
|
||||
return peelExpression(ce.getExpression());
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
public List<Expression> traceVariableAll(Expression expr) {
|
||||
return dataFlowModel.getReachingDefinitions(expr);
|
||||
}
|
||||
@@ -82,6 +92,62 @@ public class VariableTracer {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getFieldType(TypeDeclaration td, String fieldName, CodebaseContext context, java.util.Set<String> visited) {
|
||||
if (td == null) return null;
|
||||
String typeFqn = context.getFqn(td);
|
||||
if (!visited.add(typeFqn)) return null;
|
||||
|
||||
for (FieldDeclaration fd : td.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||
if (frag.getName().getIdentifier().equals(fieldName)) {
|
||||
return fd.getType().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String superFqn = context.getSuperclassFqn(td);
|
||||
if (superFqn != null) {
|
||||
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||
if (superTd != null) {
|
||||
String result = getFieldType(superTd, fieldName, context, visited);
|
||||
if (result != null) return result;
|
||||
}
|
||||
}
|
||||
|
||||
for (Object intfObj : td.superInterfaceTypes()) {
|
||||
Type intfType = (Type) intfObj;
|
||||
String intfName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(intfType);
|
||||
String resolvedIntf = resolveTypeFqn(intfName, context, td);
|
||||
if (resolvedIntf != null) {
|
||||
TypeDeclaration intfTd = context.getTypeDeclaration(resolvedIntf);
|
||||
if (intfTd != null) {
|
||||
String result = getFieldType(intfTd, fieldName, context, visited);
|
||||
if (result != null) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveTypeFqn(String simpleName, CodebaseContext context, TypeDeclaration td) {
|
||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
||||
if (cu != null) {
|
||||
for (Object impObj : cu.imports()) {
|
||||
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||
String name = imp.getName().getFullyQualifiedName();
|
||||
if (name.endsWith("." + simpleName)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
if (cu.getPackage() != null) {
|
||||
return cu.getPackage().getName().getFullyQualifiedName() + "." + simpleName;
|
||||
}
|
||||
}
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
|
||||
if (methodFqn == null) return null;
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
@@ -200,15 +266,9 @@ public class VariableTracer {
|
||||
if (foundType[0] != null) return foundType[0];
|
||||
}
|
||||
|
||||
// Fallback to fields
|
||||
for (FieldDeclaration fd : td.getFields()) {
|
||||
for (Object fragObj : fd.fragments()) {
|
||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
||||
if (frag.getName().getIdentifier().equals(cleanFieldName)) {
|
||||
return fd.getType().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to fields including superclasses
|
||||
String fieldType = getFieldType(td, cleanFieldName, context, new java.util.HashSet<>());
|
||||
if (fieldType != null) return fieldType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,14 +300,14 @@ public class VariableTracer {
|
||||
@Override
|
||||
public boolean visit(VariableDeclarationFragment node) {
|
||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
||||
initializers.add(node.getInitializer());
|
||||
initializers.add(peelExpression(node.getInitializer()));
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
@Override
|
||||
public boolean visit(Assignment node) {
|
||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
||||
initializers.add(node.getRightHandSide());
|
||||
initializers.add(peelExpression(node.getRightHandSide()));
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
@@ -266,14 +326,16 @@ public class VariableTracer {
|
||||
stringified.add(traced.toString());
|
||||
}
|
||||
}
|
||||
if (stringified.size() == 1) return stringified.get(0);
|
||||
if (stringified.size() == 1) {
|
||||
return stringified.get(0).replaceAll("->\\s*yield\\s+", "-> ");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < stringified.size() - 1; i++) {
|
||||
sb.append("true ? ").append(stringified.get(i)).append(" : ");
|
||||
}
|
||||
sb.append(stringified.get(stringified.size() - 1));
|
||||
return sb.toString();
|
||||
return sb.toString().replaceAll("->\\s*yield\\s+", "-> ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,6 +447,33 @@ public class VariableTracer {
|
||||
public Map<String, String> buildParameterValuesMap(String caller, String target, Map<String, List<CallEdge>> callGraph, List<String> path, int pathIndex) {
|
||||
Map<String, String> paramValues = new HashMap<>();
|
||||
List<CallEdge> edges = callGraph.get(caller);
|
||||
|
||||
boolean hasEdge = false;
|
||||
if (edges != null) {
|
||||
for (CallEdge edge : edges) {
|
||||
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
||||
hasEdge = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bridge polymorphic interfaces to implementations without graph edges
|
||||
if (!hasEdge) {
|
||||
String callerSimple = caller.contains(".") ? caller.substring(caller.lastIndexOf('.') + 1) : caller;
|
||||
String targetSimple = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
||||
if (callerSimple.equals(targetSimple)) {
|
||||
List<String> callerParams = getParameterNames(caller);
|
||||
List<String> targetParams = getParameterNames(target);
|
||||
if (callerParams != null && targetParams != null && callerParams.size() == targetParams.size()) {
|
||||
for (int i = 0; i < callerParams.size(); i++) {
|
||||
paramValues.put(targetParams.get(i), callerParams.get(i));
|
||||
}
|
||||
return paramValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edges == null) {
|
||||
return paramValues;
|
||||
}
|
||||
@@ -425,12 +514,40 @@ public class VariableTracer {
|
||||
return paramValues;
|
||||
}
|
||||
|
||||
private List<String> getParameterNames(String methodFqn) {
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
if (lastDot < 0) return null;
|
||||
String className = methodFqn.substring(0, lastDot);
|
||||
String methodName = methodFqn.substring(lastDot + 1);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
||||
if (td == null) return null;
|
||||
|
||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||
if (md == null) return null;
|
||||
|
||||
List<String> paramNames = new ArrayList<>();
|
||||
for (Object paramObj : md.parameters()) {
|
||||
SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj;
|
||||
paramNames.add(param.getName().getIdentifier());
|
||||
}
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
public String resolveArgumentValue(String argValue, String callerMethod, List<String> path, int pathIndex, Map<String, List<CallEdge>> callGraph) {
|
||||
if (argValue == null) return null;
|
||||
if (argValue.contains(".") || argValue.startsWith("new ") || argValue.matches(".*[0-9].*")) {
|
||||
if (argValue.contains(".") || argValue.startsWith("new ") || argValue.matches(".*[0-9].*") || (argValue.startsWith("\"") && argValue.endsWith("\""))) {
|
||||
return argValue;
|
||||
}
|
||||
|
||||
String tracedLocal = traceLocalVariable(callerMethod, argValue);
|
||||
if (tracedLocal != null && !tracedLocal.equals(argValue)) {
|
||||
if (tracedLocal.contains(".") || tracedLocal.startsWith("new ") || tracedLocal.matches(".*[0-9].*") || tracedLocal.contains("(") || (tracedLocal.startsWith("\"") && tracedLocal.endsWith("\""))) {
|
||||
return tracedLocal;
|
||||
}
|
||||
argValue = tracedLocal;
|
||||
}
|
||||
|
||||
int callerIndex = path.indexOf(callerMethod);
|
||||
if (callerIndex <= 0) return argValue;
|
||||
|
||||
@@ -489,14 +606,39 @@ public class VariableTracer {
|
||||
if (simpleClassNeighbor.equals(simpleClassTarget)) return true;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
ASTNode parent = node.getParent();
|
||||
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
||||
@@ -513,55 +655,6 @@ public class VariableTracer {
|
||||
return (TypeDeclaration) parent;
|
||||
}
|
||||
|
||||
private int getReturnedParameterIndex(MethodDeclaration md) {
|
||||
if (md.getBody() == null) return -1;
|
||||
List<?> parameters = md.parameters();
|
||||
if (parameters.isEmpty()) return -1;
|
||||
|
||||
final List<String> returnedExprs = new ArrayList<>();
|
||||
md.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(ReturnStatement node) {
|
||||
if (node.getExpression() != null) {
|
||||
returnedExprs.add(node.getExpression().toString());
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
|
||||
if (returnedExprs.size() == 1) {
|
||||
String retStr = returnedExprs.get(0);
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
if (parameters.get(i) instanceof SingleVariableDeclaration svd) {
|
||||
if (svd.getName().getIdentifier().equals(retStr)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String resolveTargetMethodFqn(MethodInvocation mi, String currentMethodFqn) {
|
||||
if (currentMethodFqn == null || !currentMethodFqn.contains(".")) return null;
|
||||
String currentClass = currentMethodFqn.substring(0, currentMethodFqn.lastIndexOf('.'));
|
||||
if (mi.getExpression() == null || mi.getExpression().toString().equals("this")) {
|
||||
return currentClass + "." + mi.getName().getIdentifier();
|
||||
}
|
||||
if (mi.getExpression() instanceof SimpleName sn) {
|
||||
String declaredType = getVariableDeclaredType(currentMethodFqn, sn.getIdentifier());
|
||||
if (declaredType != null) {
|
||||
return declaredType + "." + mi.getName().getIdentifier();
|
||||
}
|
||||
} else if (mi.getExpression() instanceof FieldAccess fa) {
|
||||
String declaredType = getVariableDeclaredType(currentMethodFqn, fa.getName().getIdentifier());
|
||||
if (declaredType != null) {
|
||||
return declaredType + "." + mi.getName().getIdentifier();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth, String methodFqn) {
|
||||
if (depth > 5) return mi;
|
||||
|
||||
@@ -579,7 +672,7 @@ public class VariableTracer {
|
||||
if (localMd != null) {
|
||||
int paramIdx = getReturnedParameterIndex(localMd);
|
||||
if (paramIdx >= 0 && paramIdx < mi.arguments().size()) {
|
||||
Expression arg = (Expression) mi.arguments().get(paramIdx);
|
||||
Expression arg = peelExpression((Expression) mi.arguments().get(paramIdx));
|
||||
if (arg instanceof MethodInvocation innerMi) {
|
||||
return unwrapMethodInvocation(innerMi, depth + 1, methodFqn);
|
||||
}
|
||||
@@ -612,7 +705,7 @@ public class VariableTracer {
|
||||
return mi;
|
||||
}
|
||||
|
||||
Expression arg = (Expression) mi.arguments().get(0);
|
||||
Expression arg = peelExpression((Expression) mi.arguments().get(0));
|
||||
if (arg instanceof MethodInvocation innerMi) {
|
||||
return unwrapMethodInvocation(innerMi, depth + 1, methodFqn);
|
||||
}
|
||||
@@ -620,4 +713,53 @@ public class VariableTracer {
|
||||
}
|
||||
return mi;
|
||||
}
|
||||
|
||||
private String resolveTargetMethodFqn(MethodInvocation mi, String currentMethodFqn) {
|
||||
if (currentMethodFqn == null || !currentMethodFqn.contains(".")) return null;
|
||||
String currentClass = currentMethodFqn.substring(0, currentMethodFqn.lastIndexOf('.'));
|
||||
if (mi.getExpression() == null || mi.getExpression().toString().equals("this")) {
|
||||
return currentClass + "." + mi.getName().getIdentifier();
|
||||
}
|
||||
if (mi.getExpression() instanceof SimpleName sn) {
|
||||
String declaredType = getVariableDeclaredType(currentMethodFqn, sn.getIdentifier());
|
||||
if (declaredType != null) {
|
||||
return declaredType + "." + mi.getName().getIdentifier();
|
||||
}
|
||||
} else if (mi.getExpression() instanceof FieldAccess fa) {
|
||||
String declaredType = getVariableDeclaredType(currentMethodFqn, fa.getName().getIdentifier());
|
||||
if (declaredType != null) {
|
||||
return declaredType + "." + mi.getName().getIdentifier();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getReturnedParameterIndex(MethodDeclaration md) {
|
||||
if (md.getBody() == null) return -1;
|
||||
List<?> parameters = md.parameters();
|
||||
if (parameters.isEmpty()) return -1;
|
||||
|
||||
final List<String> returnedExprs = new ArrayList<>();
|
||||
md.getBody().accept(new ASTVisitor() {
|
||||
@Override
|
||||
public boolean visit(ReturnStatement node) {
|
||||
if (node.getExpression() != null) {
|
||||
returnedExprs.add(node.getExpression().toString());
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
});
|
||||
|
||||
if (returnedExprs.size() == 1) {
|
||||
String retStr = returnedExprs.get(0);
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
if (parameters.get(i) instanceof SingleVariableDeclaration svd) {
|
||||
if (svd.getName().getIdentifier().equals(retStr)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ public class SpringContextScanner extends ASTVisitor {
|
||||
ITypeBinding typeBinding = node.resolveBinding();
|
||||
if (typeBinding == null) return true;
|
||||
|
||||
if (isSpringComponent(typeBinding)) {
|
||||
if (isSpringComponent(typeBinding, node)) {
|
||||
SpringBean bean = SpringBean.builder()
|
||||
.typeFqn(typeBinding.getQualifiedName())
|
||||
.assignableTypes(getAssignableTypes(typeBinding))
|
||||
.isPrimary(hasAnnotation(typeBinding, "org.springframework.context.annotation.Primary"))
|
||||
.isPrimary(hasPrimary(typeBinding, node))
|
||||
.qualifiers(extractQualifiers(typeBinding))
|
||||
.order(extractOrder(typeBinding))
|
||||
.declaringClassFqn(typeBinding.getQualifiedName())
|
||||
@@ -73,10 +73,10 @@ public class SpringContextScanner extends ASTVisitor {
|
||||
SpringBean bean = SpringBean.builder()
|
||||
.typeFqn(concreteType[0])
|
||||
.assignableTypes(assignables)
|
||||
.isPrimary(hasAnnotation(methodBinding, "org.springframework.context.annotation.Primary"))
|
||||
.isPrimary(hasPrimaryMethod(methodBinding, node))
|
||||
.qualifiers(extractQualifiers(methodBinding))
|
||||
.order(extractOrder(methodBinding))
|
||||
.declaringClassFqn(methodBinding.getDeclaringClass().getQualifiedName())
|
||||
.declaringClassFqn(methodBinding.getDeclaringClass() != null ? methodBinding.getDeclaringClass().getQualifiedName() : null)
|
||||
.factoryMethodName(node.getName().getIdentifier())
|
||||
.build();
|
||||
|
||||
@@ -97,11 +97,50 @@ public class SpringContextScanner extends ASTVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSpringComponent(ITypeBinding binding) {
|
||||
return hasMetaAnnotation(binding, "org.springframework.stereotype.Component") ||
|
||||
hasMetaAnnotation(binding, "org.springframework.web.bind.annotation.RestController");
|
||||
private boolean isSpringComponent(ITypeBinding binding, TypeDeclaration node) {
|
||||
if (hasMetaAnnotation(binding, "org.springframework.stereotype.Component") ||
|
||||
hasMetaAnnotation(binding, "org.springframework.web.bind.annotation.RestController") ||
|
||||
hasAnnotation(binding, "org.springframework.stereotype.Component") ||
|
||||
hasAnnotation(binding, "org.springframework.stereotype.Service") ||
|
||||
hasAnnotation(binding, "org.springframework.stereotype.Repository") ||
|
||||
hasAnnotation(binding, "org.springframework.stereotype.Controller") ||
|
||||
hasAnnotation(binding, "org.springframework.web.bind.annotation.RestController") ||
|
||||
hasAnnotation(binding, "org.springframework.context.annotation.Configuration")) {
|
||||
return true;
|
||||
}
|
||||
for (Object modObj : node.modifiers()) {
|
||||
if (modObj instanceof Annotation ann) {
|
||||
String name = ann.getTypeName().getFullyQualifiedName();
|
||||
if (name.equals("Component") || name.equals("Service") || name.equals("Repository") ||
|
||||
name.equals("Controller") || name.equals("RestController") || name.equals("Configuration")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasPrimary(ITypeBinding binding, TypeDeclaration node) {
|
||||
if (hasAnnotation(binding, "org.springframework.context.annotation.Primary")) return true;
|
||||
for (Object modObj : node.modifiers()) {
|
||||
if (modObj instanceof Annotation ann) {
|
||||
String name = ann.getTypeName().getFullyQualifiedName();
|
||||
if (name.equals("Primary")) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasPrimaryMethod(IMethodBinding binding, MethodDeclaration node) {
|
||||
if (hasAnnotation(binding, "org.springframework.context.annotation.Primary")) return true;
|
||||
for (Object modObj : node.modifiers()) {
|
||||
if (modObj instanceof Annotation ann) {
|
||||
String name = ann.getTypeName().getFullyQualifiedName();
|
||||
if (name.equals("Primary")) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private boolean hasMetaAnnotation(ITypeBinding binding, String targetFqn) {
|
||||
return checkMetaAnnotation(binding.getAnnotations(), targetFqn, new HashSet<>());
|
||||
}
|
||||
@@ -202,9 +241,15 @@ public class SpringContextScanner extends ASTVisitor {
|
||||
|
||||
private String extractStereotypeValue(ITypeBinding binding) {
|
||||
for (IAnnotationBinding ann : binding.getAnnotations()) {
|
||||
// Check if annotation itself is meta-annotated with @Component
|
||||
if (ann.getAnnotationType() == null) continue;
|
||||
String fqn = ann.getAnnotationType().getQualifiedName();
|
||||
if (checkMetaAnnotation(new IAnnotationBinding[]{ann}, "org.springframework.stereotype.Component", new HashSet<>()) ||
|
||||
"org.springframework.stereotype.Component".equals(ann.getAnnotationType().getQualifiedName())) {
|
||||
"org.springframework.stereotype.Component".equals(fqn) ||
|
||||
"org.springframework.stereotype.Service".equals(fqn) ||
|
||||
"org.springframework.stereotype.Repository".equals(fqn) ||
|
||||
"org.springframework.stereotype.Controller".equals(fqn) ||
|
||||
"org.springframework.web.bind.annotation.RestController".equals(fqn) ||
|
||||
"org.springframework.context.annotation.Configuration".equals(fqn)) {
|
||||
for (IMemberValuePairBinding pair : ann.getDeclaredMemberValuePairs()) {
|
||||
if ("value".equals(pair.getName()) && pair.getValue() instanceof String) {
|
||||
return (String) pair.getValue();
|
||||
|
||||
@@ -4,6 +4,9 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SpringDependencyResolver {
|
||||
private final SpringBeanRegistry registry;
|
||||
|
||||
@@ -21,16 +24,16 @@ public class SpringDependencyResolver {
|
||||
*/
|
||||
public List<SpringBean> resolve(String requiredTypeFqn, String qualifier, String injectionName) {
|
||||
if (requiredTypeFqn == null) return new ArrayList<>();
|
||||
log.debug("RESOLVING {} qual={} injectionName={}", requiredTypeFqn, qualifier, injectionName);
|
||||
|
||||
// 1. Filter by Type (Exact FQN or Assignable Type)
|
||||
System.out.println("RESOLVING " + requiredTypeFqn + " qual=" + qualifier + " injectionName=" + injectionName);
|
||||
List<SpringBean> candidates = registry.getBeans().stream()
|
||||
.filter(bean -> requiredTypeFqn.equals(bean.getTypeFqn()) || bean.getAssignableTypes().contains(requiredTypeFqn))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
System.out.println("CANDIDATES AFTER TYPE: " + candidates.stream().map(c -> c.getTypeFqn() + " (primary=" + c.isPrimary() + " names=" + c.getBeanNames() + " qual=" + c.getQualifiers() + ")").collect(Collectors.toList()));
|
||||
log.debug("CANDIDATES AFTER TYPE: {}", candidates.stream().map(c -> c.getTypeFqn() + " (primary=" + c.isPrimary() + " names=" + c.getBeanNames() + " qual=" + c.getQualifiers() + ")").collect(Collectors.toList()));
|
||||
if (candidates.isEmpty() || candidates.size() == 1) {
|
||||
System.out.println("RETURNING: " + candidates.size());
|
||||
log.debug("RETURNING: {}", candidates.size());
|
||||
return candidates;
|
||||
}
|
||||
|
||||
@@ -81,7 +84,7 @@ public class SpringDependencyResolver {
|
||||
}
|
||||
|
||||
// Return whatever candidates remain (could be ambiguous)
|
||||
System.out.println("RETURNING: " + candidates.size());
|
||||
log.debug("RETURNING: {}", candidates.size());
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,15 +239,31 @@ public class AstTransitionParser {
|
||||
} else {
|
||||
t.setEvent(Event.of(QuotedExpression.of(resolved).toStringWithoutQuotes()));
|
||||
}
|
||||
if (args.size() > 1) {
|
||||
Expression actionArg = resolveArg((Expression) args.get(1), argsMap);
|
||||
parseAction(actionArg, t, cu, context);
|
||||
}
|
||||
}
|
||||
case "guard", "guardExpression" -> {
|
||||
Expression resolved = resolveArg((Expression) args.get(0), argsMap);
|
||||
parseGuard(resolved, t, cu, context);
|
||||
}
|
||||
case "guards" -> {
|
||||
args.forEach(arg -> {
|
||||
Expression resolved = resolveArg((Expression) arg, argsMap);
|
||||
parseGuard(resolved, t, cu, context);
|
||||
});
|
||||
}
|
||||
case "action" -> {
|
||||
Expression resolved = resolveArg((Expression) args.get(0), argsMap);
|
||||
parseAction(resolved, t, cu, context);
|
||||
}
|
||||
case "actions" -> {
|
||||
args.forEach(arg -> {
|
||||
Expression resolved = resolveArg((Expression) arg, argsMap);
|
||||
parseAction(resolved, t, cu, context);
|
||||
});
|
||||
}
|
||||
case "timer", "timerOnce" -> {
|
||||
Expression resolved = resolveArg((Expression) args.get(0), argsMap);
|
||||
t.setEvent(Event.of(methodName + "(" + resolved.toString() + ")", methodName + "(" + resolved.toString() + ")"));
|
||||
@@ -521,7 +537,7 @@ public class AstTransitionParser {
|
||||
TypeDeclaration td = findEnclosingType(quotedExpr.getExpression());
|
||||
String fqn = td != null ? context.getFqn(td) : null;
|
||||
String sourceFile = fqn != null ? context.getRelativePath(fqn) : null;
|
||||
t.setGuard(Guard.of(quotedExpr.toStringWithoutQuotes(), isLambdaOrAnonymous(quotedExpr.getExpression()), internalLogic, lineNumber, fqn, sourceFile));
|
||||
t.getGuards().add(Guard.of(quotedExpr.toStringWithoutQuotes(), isLambdaOrAnonymous(quotedExpr.getExpression()), internalLogic, lineNumber, fqn, sourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,4 +95,26 @@ public final class AstUtils {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String findConditionConstraint(org.eclipse.jdt.core.dom.ASTNode node) {
|
||||
java.util.List<String> conditions = new java.util.ArrayList<>();
|
||||
org.eclipse.jdt.core.dom.ASTNode current = node;
|
||||
while (current != null && !(current instanceof org.eclipse.jdt.core.dom.MethodDeclaration)) {
|
||||
org.eclipse.jdt.core.dom.ASTNode parent = current.getParent();
|
||||
if (parent instanceof org.eclipse.jdt.core.dom.IfStatement ifStmt) {
|
||||
if (ifStmt.getExpression() != current) {
|
||||
org.eclipse.jdt.core.dom.Expression cond = ifStmt.getExpression();
|
||||
if (current == ifStmt.getElseStatement()) {
|
||||
conditions.add("!(" + cond.toString() + ")");
|
||||
} else {
|
||||
conditions.add(cond.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
if (conditions.isEmpty()) return null;
|
||||
java.util.Collections.reverse(conditions);
|
||||
return String.join(" && ", conditions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import click.kamil.springstatemachineexporter.spi.SourceContext;
|
||||
|
||||
@Slf4j
|
||||
public class CodebaseContext {
|
||||
public class CodebaseContext implements SourceContext {
|
||||
private final Map<String, CompilationUnit> classes = new HashMap<>();
|
||||
private final Map<String, Path> classPaths = new HashMap<>();
|
||||
private final Map<String, String> simpleNameToFqn = new HashMap<>();
|
||||
@@ -41,11 +42,21 @@ public class CodebaseContext {
|
||||
private List<LibraryHint> libraryHints = new ArrayList<>();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private Path projectRoot;
|
||||
private final Map<String, Object> cache = new HashMap<>();
|
||||
|
||||
public Map<String, Object> getCache() {
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void setProjectRoot(Path root) {
|
||||
this.projectRoot = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getProjectRoot() {
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
public String getRelativePath(Path fullPath) {
|
||||
if (projectRoot != null && fullPath.isAbsolute() && fullPath.startsWith(projectRoot)) {
|
||||
return projectRoot.relativize(fullPath).toString();
|
||||
@@ -83,6 +94,10 @@ public class CodebaseContext {
|
||||
this.classpath = classpath.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public List<String> getClasspath() {
|
||||
return java.util.Arrays.asList(classpath);
|
||||
}
|
||||
|
||||
public void setSourcepath(List<String> sourcepath) {
|
||||
this.sourcepath = sourcepath.toArray(new String[0]);
|
||||
}
|
||||
@@ -285,17 +300,24 @@ public class CodebaseContext {
|
||||
}
|
||||
|
||||
public List<String> getImplementations(String interfaceName) {
|
||||
String cleanName = interfaceName;
|
||||
if (interfaceName != null && interfaceName.contains("<")) {
|
||||
cleanName = interfaceName.substring(0, interfaceName.indexOf('<'));
|
||||
}
|
||||
Set<String> allImpls = new HashSet<>();
|
||||
collectImplementations(interfaceName, allImpls, new HashSet<>());
|
||||
System.out.println("GET IMPLEMENTATIONS FOR: " + interfaceName + " -> " + allImpls);
|
||||
collectImplementations(cleanName, allImpls, new HashSet<>());
|
||||
return new ArrayList<>(allImpls);
|
||||
}
|
||||
|
||||
private void collectImplementations(String typeName, Set<String> results, Set<String> visited) {
|
||||
if (!visited.add(typeName)) return;
|
||||
String cleanName = typeName;
|
||||
if (typeName != null && typeName.contains("<")) {
|
||||
cleanName = typeName.substring(0, typeName.indexOf('<'));
|
||||
}
|
||||
if (!visited.add(cleanName)) return;
|
||||
|
||||
// Try direct match
|
||||
List<String> directImpls = interfaceToImpls.get(typeName);
|
||||
List<String> directImpls = interfaceToImpls.get(cleanName);
|
||||
|
||||
// Try FQN match if input was simple name
|
||||
if (directImpls == null) {
|
||||
@@ -367,10 +389,12 @@ public class CodebaseContext {
|
||||
cleanName = cleanName.trim();
|
||||
|
||||
List<String> values = enumValues.get(cleanName);
|
||||
if (values != null) return values;
|
||||
if (values == null) {
|
||||
String fqn = simpleNameToFqn.get(cleanName);
|
||||
if (fqn != null) return enumValues.get(fqn);
|
||||
return null;
|
||||
if (fqn != null) values = enumValues.get(fqn);
|
||||
}
|
||||
log.debug("getEnumValues called for: {} resolved to {}. Returns: {}", fqnOrSimpleName, cleanName, values);
|
||||
return values;
|
||||
}
|
||||
|
||||
public State resolveState(Expression expr, CompilationUnit cu) {
|
||||
|
||||
@@ -3,12 +3,25 @@ package click.kamil.springstatemachineexporter.ast.common;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
import java.util.*;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class JdtDataFlowModel implements DataFlowModel {
|
||||
private final CodebaseContext context;
|
||||
private final Map<MethodDeclaration, ControlFlowGraph> cfgCache = new HashMap<>();
|
||||
private final Map<MethodDeclaration, ReachingDefinitions> rdCache = new HashMap<>();
|
||||
private final Map<ASTNode, Map<IVariableBinding, Expression>> objectFieldBindings = new HashMap<>();
|
||||
|
||||
private static final ThreadLocal<List<String>> CURRENT_PATH = new ThreadLocal<>();
|
||||
|
||||
public static void setCurrentPath(List<String> path) {
|
||||
CURRENT_PATH.set(path);
|
||||
}
|
||||
|
||||
public static void clearCurrentPath() {
|
||||
CURRENT_PATH.remove();
|
||||
}
|
||||
|
||||
public JdtDataFlowModel(CodebaseContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
@@ -82,15 +95,17 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Handle Parameter mapping for SimpleName
|
||||
// 3. Handle Parameter Variable Bindings (Propagate values through parameters)
|
||||
if (expr instanceof SimpleName sn) {
|
||||
IBinding b = sn.resolveBinding();
|
||||
if (b instanceof IVariableBinding paramVb && paramBindings.containsKey(paramVb)) {
|
||||
if (b instanceof IVariableBinding paramVb) {
|
||||
if (paramBindings.containsKey(paramVb)) {
|
||||
Expression argExpr = paramBindings.get(paramVb);
|
||||
List<Expression> resolved = getReachingDefinitions(argExpr, visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
visited.remove(expr);
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Local Reaching Definitions
|
||||
MethodDeclaration md = findEnclosingMethod(sn);
|
||||
@@ -134,6 +149,44 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// Handle SwitchExpression
|
||||
if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) {
|
||||
List<Expression> results = new ArrayList<>();
|
||||
List<Expression> selectorDefs = getReachingDefinitions(se.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||
String selectorVal = null;
|
||||
if (!selectorDefs.isEmpty()) {
|
||||
selectorVal = resolveValue(selectorDefs.get(0), context);
|
||||
}
|
||||
|
||||
boolean active = (selectorVal == null);
|
||||
for (Object stmtObj : se.statements()) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.SwitchCase sc) {
|
||||
if (selectorVal != null) {
|
||||
active = false;
|
||||
for (Object expObj : sc.expressions()) {
|
||||
Expression caseExpr = (Expression) expObj;
|
||||
String caseVal = resolveValue(caseExpr, context);
|
||||
if (caseVal != null && (caseVal.equals(selectorVal) || selectorVal.endsWith("." + caseVal) || caseVal.endsWith("." + selectorVal))) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sc.isDefault()) {
|
||||
active = true;
|
||||
}
|
||||
}
|
||||
} else if (active) {
|
||||
if (stmtObj instanceof org.eclipse.jdt.core.dom.YieldStatement ys && ys.getExpression() != null) {
|
||||
results.addAll(getReachingDefinitions(ys.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||
} else if (stmtObj instanceof ExpressionStatement es) {
|
||||
results.addAll(getReachingDefinitions(es.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
visited.remove(expr);
|
||||
return results;
|
||||
}
|
||||
|
||||
// 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
String mName = mi.getName().getIdentifier();
|
||||
@@ -374,7 +427,7 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
MethodDeclaration cd = findMethodDeclaration(cb);
|
||||
if (cd == null || cd.getBody() == null) return;
|
||||
|
||||
Map<IVariableBinding, Expression> currentParamValues = new HashMap<>();
|
||||
Map<IVariableBinding, Expression> currentParamValues = new HashMap<>(callerParamValues);
|
||||
List<?> parameters = cd.parameters();
|
||||
int count = Math.min(parameters.size(), arguments.size());
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -387,6 +440,8 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("EVAL_CTOR: cd={}, currentParamValues={}", cd.getName(), currentParamValues);
|
||||
|
||||
List<?> statements = cd.getBody().statements();
|
||||
if (!statements.isEmpty()) {
|
||||
Object firstStmt = statements.get(0);
|
||||
@@ -425,8 +480,13 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
|
||||
if (fieldVb != null) {
|
||||
Expression resolvedRhs = resolveParamValue(rhs, currentParamValues);
|
||||
List<Expression> evaluated = getReachingDefinitions(resolvedRhs, new HashSet<>(), currentParamValues, fieldBindings, 0);
|
||||
if (!evaluated.isEmpty()) {
|
||||
fieldBindings.put(fieldVb, evaluated.get(0));
|
||||
} else {
|
||||
fieldBindings.put(fieldVb, resolvedRhs);
|
||||
}
|
||||
}
|
||||
return super.visit(assignment);
|
||||
}
|
||||
});
|
||||
@@ -533,6 +593,19 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
if (declaringClassFqn != null && !declaringClassFqn.isEmpty()) {
|
||||
// Find all known implementation subclasses in the codebase
|
||||
List<String> implClassFqns = context.getImplementations(declaringClassFqn);
|
||||
List<String> currentPath = CURRENT_PATH.get();
|
||||
if (currentPath != null && implClassFqns != null && !implClassFqns.isEmpty()) {
|
||||
Set<String> contextTypes = getCompatibleContextTypes(currentPath, declaringClassFqn);
|
||||
List<String> filteredImpls = new ArrayList<>();
|
||||
for (String implFqn : implClassFqns) {
|
||||
if (contextTypes.contains(implFqn)) {
|
||||
filteredImpls.add(implFqn);
|
||||
}
|
||||
}
|
||||
if (!filteredImpls.isEmpty()) {
|
||||
implClassFqns = filteredImpls;
|
||||
}
|
||||
}
|
||||
if (implClassFqns != null && !implClassFqns.isEmpty()) {
|
||||
for (String implFqn : implClassFqns) {
|
||||
TypeDeclaration td = context.getTypeDeclaration(implFqn);
|
||||
@@ -895,4 +968,74 @@ public class JdtDataFlowModel implements DataFlowModel {
|
||||
}
|
||||
return firstVal != null ? firstVal : context.resolveExpression(expr);
|
||||
}
|
||||
|
||||
private Set<String> getCompatibleContextTypes(List<String> currentPath, String declaringClassFqn) {
|
||||
Set<String> contextTypes = new HashSet<>();
|
||||
List<String> implementations = context.getImplementations(declaringClassFqn);
|
||||
|
||||
// 1. Direct class name from the path itself (prioritize concrete implementations in the path)
|
||||
for (String methodFqn : currentPath) {
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String pathClass = methodFqn.substring(0, lastDot);
|
||||
if (implementations.contains(pathClass)) {
|
||||
contextTypes.add(pathClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!contextTypes.isEmpty()) {
|
||||
return contextTypes;
|
||||
}
|
||||
|
||||
// 2. Fallback: Inspect fields and local variables/parameters of the classes in the path
|
||||
for (String methodFqn : currentPath) {
|
||||
int lastDot = methodFqn.lastIndexOf('.');
|
||||
if (lastDot > 0) {
|
||||
String pathClass = methodFqn.substring(0, lastDot);
|
||||
if (pathClass.equals(declaringClassFqn) || implementations.contains(pathClass)) {
|
||||
contextTypes.add(pathClass);
|
||||
}
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration(pathClass);
|
||||
if (td != null) {
|
||||
// Check fields
|
||||
for (FieldDeclaration fd : td.getFields()) {
|
||||
String fieldType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(fd.getType());
|
||||
TypeDeclaration fieldTd = context.getTypeDeclaration(fieldType);
|
||||
if (fieldTd == null) {
|
||||
CompilationUnit cu = (CompilationUnit) td.getRoot();
|
||||
fieldTd = context.getTypeDeclaration(fieldType, cu);
|
||||
}
|
||||
if (fieldTd != null) {
|
||||
String fieldFqn = context.getFqn(fieldTd);
|
||||
if (fieldFqn.equals(declaringClassFqn) || implementations.contains(fieldFqn)) {
|
||||
contextTypes.add(fieldFqn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check method parameters
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
for (Object paramObj : md.parameters()) {
|
||||
if (paramObj instanceof SingleVariableDeclaration svd) {
|
||||
String paramType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(svd.getType());
|
||||
TypeDeclaration paramTd = context.getTypeDeclaration(paramType);
|
||||
if (paramTd == null) {
|
||||
CompilationUnit cu = (CompilationUnit) td.getRoot();
|
||||
paramTd = context.getTypeDeclaration(paramType, cu);
|
||||
}
|
||||
if (paramTd != null) {
|
||||
String paramFqn = context.getFqn(paramTd);
|
||||
if (paramFqn.equals(declaringClassFqn) || implementations.contains(paramFqn)) {
|
||||
contextTypes.add(paramFqn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return contextTypes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,13 +107,19 @@ public class Dot implements StateMachineExporter {
|
||||
}
|
||||
}
|
||||
|
||||
// Guard
|
||||
if (t.getGuard() != null && !t.getGuard().expression().isBlank()) {
|
||||
// Guards
|
||||
if (t.getGuards() != null && !t.getGuards().isEmpty()) {
|
||||
if (!label.isEmpty())
|
||||
label.append(" ");
|
||||
String guardText = options.isUseLambdaGuards() && t.getGuard().isLambda() ? "λ"
|
||||
: escapeDotString(t.getGuard().expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
|
||||
label.append("[").append(guardText).append("]");
|
||||
label.append("[");
|
||||
for (int i = 0; i < t.getGuards().size(); i++) {
|
||||
if (i > 0) label.append(" && ");
|
||||
var guard = t.getGuards().get(i);
|
||||
String guardText = options.isUseLambdaGuards() && guard.isLambda() ? "λ"
|
||||
: escapeDotString(guard.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
|
||||
label.append(guardText);
|
||||
}
|
||||
label.append("]");
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
@@ -203,14 +203,21 @@ public class PlantUml implements StateMachineExporter {
|
||||
parts.add(eventStr);
|
||||
}
|
||||
}
|
||||
if (t.getGuard() != null) {
|
||||
String g = t.getGuard().expression();
|
||||
if (options.isUseLambdaGuards() && t.getGuard().isLambda()) {
|
||||
parts.add("[" + LAMBDA + "]");
|
||||
if (t.getGuards() != null && !t.getGuards().isEmpty()) {
|
||||
StringBuilder guardsBuilder = new StringBuilder();
|
||||
guardsBuilder.append("[");
|
||||
for (int i = 0; i < t.getGuards().size(); i++) {
|
||||
if (i > 0) guardsBuilder.append(" && ");
|
||||
var guard = t.getGuards().get(i);
|
||||
if (options.isUseLambdaGuards() && guard.isLambda()) {
|
||||
guardsBuilder.append(LAMBDA);
|
||||
} else {
|
||||
parts.add("[" + g.replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim() + "]");
|
||||
guardsBuilder.append(guard.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
|
||||
}
|
||||
}
|
||||
guardsBuilder.append("]");
|
||||
parts.add(guardsBuilder.toString());
|
||||
}
|
||||
if (t.getActions() != null && !t.getActions().isEmpty()) {
|
||||
parts.add("/ " + t.getActions().stream()
|
||||
.map(a -> (options.isUseLambdaGuards() && a.isLambda()) ? LAMBDA : a.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim())
|
||||
|
||||
@@ -102,12 +102,20 @@ public class Scxml implements StateMachineExporter {
|
||||
}
|
||||
|
||||
private static String getGuardText(Transition t, boolean useLambdaGuards) {
|
||||
if (t.getGuard() == null || t.getGuard().expression().isBlank())
|
||||
if (t.getGuards() == null || t.getGuards().isEmpty())
|
||||
return "";
|
||||
if (useLambdaGuards && t.getGuard().isLambda()) {
|
||||
return LAMBDA;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < t.getGuards().size(); i++) {
|
||||
if (i > 0) sb.append(" && ");
|
||||
var guard = t.getGuards().get(i);
|
||||
if (useLambdaGuards && guard.isLambda()) {
|
||||
sb.append(LAMBDA);
|
||||
} else {
|
||||
sb.append(guard.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim());
|
||||
}
|
||||
return t.getGuard().expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String escapeXml(String s) {
|
||||
|
||||
@@ -16,7 +16,7 @@ public class Transition {
|
||||
private List<State> targetStates = new ArrayList<>();
|
||||
private Event event;
|
||||
|
||||
private Guard guard;
|
||||
private List<Guard> guards = new ArrayList<>();
|
||||
private List<Action> actions = new ArrayList<>();
|
||||
|
||||
private Integer order = null; // optional order for withChoice or withJunction
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package click.kamil.springstatemachineexporter.plugin.java;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.CallGraphEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.JdtCallGraphEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.JdtIntelligenceProvider;
|
||||
import click.kamil.springstatemachineexporter.ast.app.AstTransitionParser;
|
||||
import click.kamil.springstatemachineexporter.ast.app.StateMachineAggregator;
|
||||
import click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import click.kamil.springstatemachineexporter.model.Transition;
|
||||
import click.kamil.springstatemachineexporter.spi.LanguageAnalyzerPlugin;
|
||||
import click.kamil.springstatemachineexporter.spi.SourceContext;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class JavaLanguageAnalyzerPlugin implements LanguageAnalyzerPlugin {
|
||||
|
||||
@Override
|
||||
public boolean supports(Path projectRoot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceContext parseProject(Path projectRoot, List<String> classpath, Set<Path> sourcePaths) {
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setProjectRoot(projectRoot);
|
||||
if (classpath != null && !classpath.isEmpty()) {
|
||||
context.setClasspath(classpath);
|
||||
}
|
||||
|
||||
Path hintsFile = projectRoot.resolve("hints.json");
|
||||
if (!java.nio.file.Files.exists(hintsFile)) {
|
||||
hintsFile = projectRoot.resolve("src/main/resources/hints.json");
|
||||
}
|
||||
if (java.nio.file.Files.exists(hintsFile)) {
|
||||
try {
|
||||
context.loadLibraryHints(hintsFile);
|
||||
} catch (java.io.IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> sp = sourcePaths.stream()
|
||||
.map(p -> p.resolve("src/main/java"))
|
||||
.filter(p -> java.nio.file.Files.exists(p))
|
||||
.map(Path::toString)
|
||||
.toList();
|
||||
if (sp.isEmpty()) {
|
||||
sp = sourcePaths.stream().map(Path::toString).toList();
|
||||
}
|
||||
context.setSourcepath(sp);
|
||||
|
||||
context.setResolveBindings(true);
|
||||
try {
|
||||
context.scan(sourcePaths, Collections.emptySet());
|
||||
} catch (java.io.IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallGraphEngine createCallGraphEngine(SourceContext context) {
|
||||
if (context instanceof CodebaseContext jdtContext) {
|
||||
return new JdtCallGraphEngine(jdtContext, null);
|
||||
}
|
||||
throw new IllegalArgumentException("Expected CodebaseContext");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodebaseIntelligenceProvider createIntelligenceProvider(SourceContext context) {
|
||||
if (context instanceof CodebaseContext jdtContext) {
|
||||
return new JdtIntelligenceProvider(jdtContext, jdtContext.getProjectRoot());
|
||||
}
|
||||
throw new IllegalArgumentException("Expected CodebaseContext");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AnalysisResult> extractStateMachines(SourceContext context, boolean renderChoicesAsDiamonds) {
|
||||
if (context instanceof CodebaseContext jdtContext) {
|
||||
List<AnalysisResult> results = new ArrayList<>();
|
||||
|
||||
// 1. Find entry point classes (annotated or extending adapter)
|
||||
List<TypeDeclaration> entryPoints = jdtContext.findEntryPointClasses(List.of("EnableStateMachineFactory", "EnableStateMachine"));
|
||||
for (TypeDeclaration td : entryPoints) {
|
||||
String className = jdtContext.getFqn(td);
|
||||
StateMachineAggregator aggregator = new StateMachineAggregator(jdtContext);
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
aggregator.aggregateStates(td);
|
||||
Set<String> initialStatesAst = aggregator.getInitialStates();
|
||||
Set<String> endStatesAst = aggregator.getEndStates();
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions, initialStatesAst);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions, endStatesAst);
|
||||
Set<click.kamil.springstatemachineexporter.model.State> allStates = TransitionStateUtils.findAllStates(transitions, initialStatesAst, endStatesAst);
|
||||
|
||||
if (allStates.isEmpty() && transitions.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.add(AnalysisResult.builder()
|
||||
.name(className)
|
||||
.states(allStates)
|
||||
.transitions(transitions)
|
||||
.startStates(startStates)
|
||||
.endStates(endStates)
|
||||
.renderChoicesAsDiamonds(renderChoicesAsDiamonds)
|
||||
.build());
|
||||
}
|
||||
|
||||
// 2. Find methods returning StateMachine beans
|
||||
List<MethodDeclaration> beanMethods = jdtContext.findBeanMethodsReturning(Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory"));
|
||||
for (MethodDeclaration m : beanMethods) {
|
||||
TypeDeclaration parentClass = (TypeDeclaration) m.getParent();
|
||||
String parentFqn = jdtContext.getFqn(parentClass);
|
||||
String methodName = m.getName().getIdentifier();
|
||||
String uniqueName = parentFqn + "#" + methodName;
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(m, jdtContext);
|
||||
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions, null);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions, null);
|
||||
Set<click.kamil.springstatemachineexporter.model.State> allStates = TransitionStateUtils.findAllStates(transitions, null, null);
|
||||
|
||||
if (allStates.isEmpty() && transitions.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.add(AnalysisResult.builder()
|
||||
.name(uniqueName)
|
||||
.states(allStates)
|
||||
.transitions(transitions)
|
||||
.startStates(startStates)
|
||||
.endStates(endStates)
|
||||
.renderChoicesAsDiamonds(renderChoicesAsDiamonds)
|
||||
.build());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,11 @@ import click.kamil.springstatemachineexporter.exporter.StateMachineExporter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import click.kamil.springstatemachineexporter.spi.LanguageAnalyzerPlugin;
|
||||
import click.kamil.springstatemachineexporter.spi.SourceContext;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.CompositeIntelligenceProvider;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.CallGraphEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.JdtCallGraphEngine;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
@@ -137,8 +142,57 @@ public class ExportService {
|
||||
|
||||
context.scan(allPaths, Collections.emptySet());
|
||||
|
||||
CodebaseIntelligenceProvider intelligence = new JdtIntelligenceProvider(context, inputDir);
|
||||
exportAll(context, intelligence, outputDir, selectedFormats, renderChoicesAsDiamonds, flows, machineFilter, activeProfiles != null ? activeProfiles : Collections.emptyList(), eventFormat, stateFormat);
|
||||
// Load SPI plugins dynamically
|
||||
ServiceLoader<LanguageAnalyzerPlugin> loader = ServiceLoader.load(LanguageAnalyzerPlugin.class);
|
||||
List<LanguageAnalyzerPlugin> plugins = new ArrayList<>();
|
||||
for (LanguageAnalyzerPlugin plugin : loader) {
|
||||
if (plugin.supports(inputDir)) {
|
||||
plugins.add(plugin);
|
||||
}
|
||||
}
|
||||
if (plugins.isEmpty()) {
|
||||
plugins.add(new click.kamil.springstatemachineexporter.plugin.java.JavaLanguageAnalyzerPlugin());
|
||||
}
|
||||
|
||||
List<SourceContext> sourceContexts = new ArrayList<>();
|
||||
List<CodebaseIntelligenceProvider> providers = new ArrayList<>();
|
||||
List<CallGraphEngine> engines = new ArrayList<>();
|
||||
List<AnalysisResult> extractedResults = new ArrayList<>();
|
||||
|
||||
for (LanguageAnalyzerPlugin plugin : plugins) {
|
||||
SourceContext sc = plugin.parseProject(projectRoot, context.getClasspath(), allPaths);
|
||||
sourceContexts.add(sc);
|
||||
providers.add(plugin.createIntelligenceProvider(sc));
|
||||
engines.add(plugin.createCallGraphEngine(sc));
|
||||
extractedResults.addAll(plugin.extractStateMachines(sc, renderChoicesAsDiamonds));
|
||||
}
|
||||
|
||||
CodebaseContext codebaseContext = context;
|
||||
CodebaseIntelligenceProvider unifiedIntelligence;
|
||||
if (plugins.size() == 1) {
|
||||
unifiedIntelligence = providers.get(0);
|
||||
if (sourceContexts.get(0) instanceof CodebaseContext jdtCtx) {
|
||||
codebaseContext = jdtCtx;
|
||||
}
|
||||
} else {
|
||||
unifiedIntelligence = new CompositeIntelligenceProvider(providers, engines);
|
||||
}
|
||||
|
||||
// Process all extracted state machines
|
||||
for (AnalysisResult result : extractedResults) {
|
||||
String name = result.getName();
|
||||
if (machineFilter != null && !name.contains(machineFilter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.getFlows() == null || result.getFlows().isEmpty()) {
|
||||
result.setFlows(flows);
|
||||
}
|
||||
|
||||
enrichmentService.enrich(result, codebaseContext, unifiedIntelligence);
|
||||
resolveProperties(result, activeProfiles);
|
||||
generateOutputs(outputDir, result, selectedFormats, eventFormat, stateFormat);
|
||||
}
|
||||
}
|
||||
|
||||
public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats) throws IOException {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package click.kamil.springstatemachineexporter.spi;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.service.CallGraphEngine;
|
||||
import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Service Provider Interface (SPI) for introducing support for different languages (Java, Kotlin, etc.).
|
||||
*/
|
||||
public interface LanguageAnalyzerPlugin {
|
||||
|
||||
/**
|
||||
* Determines if this plugin is capable of handling the provided project root.
|
||||
* Often checks for file extensions (.kt, .java) within the root.
|
||||
*/
|
||||
boolean supports(Path projectRoot);
|
||||
|
||||
/**
|
||||
* Parses the codebase and sets up a SourceContext loaded with the language's AST structures.
|
||||
*/
|
||||
SourceContext parseProject(Path projectRoot, List<String> classpath, Set<Path> sourcePaths);
|
||||
|
||||
/**
|
||||
* Creates a CallGraphEngine specialized for this language's syntax and paradigms.
|
||||
*/
|
||||
CallGraphEngine createCallGraphEngine(SourceContext context);
|
||||
|
||||
/**
|
||||
* Creates a CodebaseIntelligenceProvider specialized for this language to discover
|
||||
* Spring State Machine configurations, entry points, and transitions.
|
||||
*/
|
||||
CodebaseIntelligenceProvider createIntelligenceProvider(SourceContext context);
|
||||
|
||||
/**
|
||||
* Finds and extracts all Spring State Machine models (configurations and beans) from the project.
|
||||
*/
|
||||
default List<click.kamil.springstatemachineexporter.analysis.model.AnalysisResult> extractStateMachines(SourceContext context, boolean renderChoicesAsDiamonds) {
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package click.kamil.springstatemachineexporter.spi;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.LibraryHint;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An abstraction over a parsed source codebase (Java, Kotlin, etc.).
|
||||
* Provides access to common metadata required by analysis engines.
|
||||
*/
|
||||
public interface SourceContext {
|
||||
/**
|
||||
* Gets the root directory of the scanned project.
|
||||
*/
|
||||
Path getProjectRoot();
|
||||
|
||||
/**
|
||||
* Relativizes an absolute path against the project root.
|
||||
*/
|
||||
String getRelativePath(Path fullPath);
|
||||
|
||||
/**
|
||||
* Returns a relative file path for a given fully qualified class/type name.
|
||||
*/
|
||||
String getRelativePath(String fqn);
|
||||
|
||||
/**
|
||||
* Retrieves any loaded library hints (e.g. from hints.json).
|
||||
*/
|
||||
List<LibraryHint> getLibraryHints();
|
||||
|
||||
/**
|
||||
* Retrieves application properties (e.g. from application.yml).
|
||||
*/
|
||||
Map<String, Map<String, String>> getProperties();
|
||||
|
||||
/**
|
||||
* Gets an unstructured cache map that plugins can use to share data across processing phases.
|
||||
*/
|
||||
Map<String, Object> getCache();
|
||||
}
|
||||
@@ -158,6 +158,24 @@ public class GoldenUpdater {
|
||||
Path.of("state_machines/inheritance_extra_functions3_state_machine"),
|
||||
Path.of("src/test/resources/golden/G2StateMachineConfiguration"),
|
||||
"G2StateMachineConfiguration"
|
||||
),
|
||||
new TestScenario(
|
||||
"Enterprise Order State Machine",
|
||||
Path.of("state_machines/state_machine_enterprise"),
|
||||
Path.of("src/test/resources/golden/OrderStateMachineConfiguration"),
|
||||
"OrderStateMachineConfiguration"
|
||||
),
|
||||
new TestScenario(
|
||||
"Enterprise Document State Machine",
|
||||
Path.of("state_machines/state_machine_enterprise"),
|
||||
Path.of("src/test/resources/golden/DocumentStateMachineConfiguration"),
|
||||
"DocumentStateMachineConfiguration"
|
||||
),
|
||||
new TestScenario(
|
||||
"Enterprise User State Machine",
|
||||
Path.of("state_machines/state_machine_enterprise"),
|
||||
Path.of("src/test/resources/golden/UserStateMachineConfiguration"),
|
||||
"UserStateMachineConfiguration"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package click.kamil.springstatemachineexporter;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
public class TestTriggerPoint {
|
||||
public static void main(String[] args) throws Exception {
|
||||
TriggerPoint tp = TriggerPoint.builder()
|
||||
.event("PLACE_ORDER")
|
||||
.eventTypeFqn("String")
|
||||
.build();
|
||||
System.out.println("Result event: " + tp.getEvent());
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
System.out.println("JSON: " + mapper.writeValueAsString(tp));
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class TransitionLinkerEnricherTest {
|
||||
enricher.enrich(result, null, null);
|
||||
|
||||
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
|
||||
assertThat(updatedChain.getMatchedTransitions()).isNull();
|
||||
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -248,6 +248,31 @@ class TransitionLinkerEnricherTest {
|
||||
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteDeterministicallyAndUtilizeInternalCaches() {
|
||||
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"));
|
||||
|
||||
CallChain chain1 = CallChain.builder().triggerPoint(TriggerPoint.builder().event("PAY").build()).build();
|
||||
CallChain chain2 = CallChain.builder().triggerPoint(TriggerPoint.builder().event("PAY").build()).build();
|
||||
CallChain chain3 = CallChain.builder().triggerPoint(TriggerPoint.builder().event("PAY").build()).build();
|
||||
|
||||
AnalysisResult result = AnalysisResult.builder()
|
||||
.transitions(List.of(t1))
|
||||
.metadata(CodebaseMetadata.builder().callChains(List.of(chain1, chain2, chain3)).build())
|
||||
.build();
|
||||
|
||||
enricher.enrich(result, null, null);
|
||||
|
||||
// All 3 identical trigger point chains should be linked seamlessly using the memoization cache.
|
||||
for (CallChain chain : result.getMetadata().getCallChains()) {
|
||||
assertThat(chain.getMatchedTransitions()).hasSize(1);
|
||||
assertThat(chain.getMatchedTransitions().get(0).getEvent()).isEqualTo("PAY");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void shouldFilterCrossStateMachineRoutingByVertical() {
|
||||
@@ -408,4 +433,83 @@ class TransitionLinkerEnricherTest {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -125,4 +125,26 @@ class HeuristicEventMatchingEngineTest {
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchWildcardIfTypeMatches() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("event")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchWildcardIfTypeMismatches() {
|
||||
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("event")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.model.Event;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StrictFqnMatchingEngineTest {
|
||||
|
||||
private final StrictFqnMatchingEngine engine = new StrictFqnMatchingEngine();
|
||||
|
||||
@Test
|
||||
void shouldMatchExactFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("PAY")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchEnumQualifierToFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("OrderEvents.PAY")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreventCrossEnumContamination() {
|
||||
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("OrderEvents.PAY")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPreventCrossEnumContaminationWithPolymorphicEvents() {
|
||||
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("getType()")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.polymorphicEvents(List.of("OrderEvents.PAY"))
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchPolymorphicEnumQualifierToFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("getType()")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.polymorphicEvents(List.of("OrderEvents.PAY"))
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchRawStringToFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("PAY")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchRawStringToRawString() {
|
||||
Event smEvent = Event.of("PAY", "PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchPolymorphicRawStringToFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("getType()")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.polymorphicEvents(List.of("PAY"))
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchWildcards() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder().event("event").build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchMismatchedFqn() {
|
||||
Event smEvent = Event.of("CREATE", "com.example.other.OrderEvent.CREATE");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("CREATE")
|
||||
.eventTypeFqn("com.example.some.OrderEvent")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchStringTypeToEnumFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("PAY")
|
||||
.eventTypeFqn("java.lang.String")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchEnumTypeToStringFqn() {
|
||||
Event smEvent = Event.of("PAY", "PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("OrderEvents.PAY")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchValueOfWithCorrectEnum() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("OrderEvents.valueOf(str)")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchValueOfWithIncorrectEnum() {
|
||||
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("OrderEvents.valueOf(str)")
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchDynamicVariableWithMatchingTypeFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("myCustomEvt") // Not "event", "e", etc., but a custom variable name
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMatchDynamicVariableWithMismatchedTypeFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("myCustomEvt")
|
||||
.eventTypeFqn("com.example.InvoiceEvents") // Mismatched type
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchDynamicVariableWithUnknownTypeFqn() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("myCustomEvt")
|
||||
.eventTypeFqn(null) // Unknown type, fallback to true
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMatchDynamicMethodCallAsVariable() {
|
||||
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("event.getPayload()") // Dynamic method call
|
||||
.eventTypeFqn("com.example.OrderEvents")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -482,4 +482,46 @@ public class ConstantResolverTest {
|
||||
assertThat(result).contains("OrderEvents.B2");
|
||||
assertThat(result).contains("OrderEvents.DEF");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveEnumValueOfWithConstantString(@TempDir Path tempDir) throws IOException {
|
||||
Path dir = tempDir.resolve("com/example");
|
||||
Files.createDirectories(dir);
|
||||
Files.writeString(dir.resolve("OrderEvent.java"),
|
||||
"package com.example;\n" +
|
||||
"public enum OrderEvent { RECEIVED, SHIPPED }\n");
|
||||
Files.writeString(dir.resolve("Caller.java"),
|
||||
"package com.example;\n" +
|
||||
"public class Caller {\n" +
|
||||
" public void call() {\n" +
|
||||
" OrderEvent e1 = OrderEvent.valueOf(\"RECEIVED\");\n" +
|
||||
" OrderEvent e2 = OrderEvent.valueOf(getEventString());\n" +
|
||||
" }\n" +
|
||||
" private String getEventString() {\n" +
|
||||
" return \"SHIPPED\";\n" +
|
||||
" }\n" +
|
||||
"}");
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
|
||||
MethodDeclaration callMethod = callerTd.getMethods()[0];
|
||||
|
||||
VariableDeclarationStatement vds1 = (VariableDeclarationStatement) callMethod.getBody().statements().get(0);
|
||||
VariableDeclarationFragment fragment1 = (VariableDeclarationFragment) vds1.fragments().get(0);
|
||||
MethodInvocation mi1 = (MethodInvocation) fragment1.getInitializer();
|
||||
|
||||
VariableDeclarationStatement vds2 = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
|
||||
VariableDeclarationFragment fragment2 = (VariableDeclarationFragment) vds2.fragments().get(0);
|
||||
MethodInvocation mi2 = (MethodInvocation) fragment2.getInitializer();
|
||||
|
||||
ConstantResolver resolver = new ConstantResolver();
|
||||
|
||||
String result1 = resolver.resolve(mi1, context);
|
||||
assertThat(result1).isEqualTo("com.example.OrderEvent.RECEIVED");
|
||||
|
||||
String result2 = resolver.resolve(mi2, context);
|
||||
assertThat(result2).isEqualTo("com.example.OrderEvent.SHIPPED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RelativePathTest {
|
||||
@Test
|
||||
public void testRelativePathDirectory(@TempDir Path tempDir) throws Exception {
|
||||
Path rootDir = tempDir.resolve("root");
|
||||
Path childDir = tempDir.resolve("child");
|
||||
Files.createDirectories(rootDir);
|
||||
Files.createDirectories(childDir);
|
||||
|
||||
Files.writeString(rootDir.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>root</artifactId></project>");
|
||||
Files.writeString(childDir.resolve("pom.xml"), "<project><parent><relativePath>../root</relativePath></parent></project>");
|
||||
|
||||
SiblingDependencyResolver resolver = new SiblingDependencyResolver();
|
||||
Set<Path> siblings = resolver.resolveSiblings(childDir);
|
||||
assertTrue(siblings.contains(rootDir), "Should resolve parent directory from <relativePath>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelativePathFile(@TempDir Path tempDir) throws Exception {
|
||||
Path rootDir = tempDir.resolve("root");
|
||||
Path childDir = tempDir.resolve("child");
|
||||
Files.createDirectories(rootDir);
|
||||
Files.createDirectories(childDir);
|
||||
|
||||
Files.writeString(rootDir.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>root</artifactId></project>");
|
||||
Files.writeString(childDir.resolve("pom.xml"), "<project><parent><relativePath>../root/pom.xml</relativePath></parent></project>");
|
||||
|
||||
SiblingDependencyResolver resolver = new SiblingDependencyResolver();
|
||||
Set<Path> siblings = resolver.resolveSiblings(childDir);
|
||||
assertTrue(siblings.contains(rootDir), "Should resolve parent directory from <relativePath> file");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SiblingDependencyResolverTest {
|
||||
|
||||
@Test
|
||||
void shouldResolveGradleSiblingsTransitively(@TempDir Path tempDir) throws IOException {
|
||||
// Create root project
|
||||
Path rootDir = tempDir.resolve("my-root-project");
|
||||
Files.createDirectories(rootDir);
|
||||
Files.writeString(rootDir.resolve("settings.gradle"), "include 'module-a', 'module-b', 'module-c'");
|
||||
|
||||
// Create module A (depends on B)
|
||||
Path moduleA = rootDir.resolve("module-a");
|
||||
Files.createDirectories(moduleA);
|
||||
Files.writeString(moduleA.resolve("build.gradle"), "dependencies { implementation project(':module-b') }");
|
||||
|
||||
// Create module B (depends on C)
|
||||
Path moduleB = rootDir.resolve("module-b");
|
||||
Files.createDirectories(moduleB);
|
||||
Files.writeString(moduleB.resolve("build.gradle"), "dependencies { implementation project(':module-c') }");
|
||||
|
||||
// Create module C (no deps)
|
||||
Path moduleC = rootDir.resolve("module-c");
|
||||
Files.createDirectories(moduleC);
|
||||
Files.writeString(moduleC.resolve("build.gradle"), "");
|
||||
|
||||
SiblingDependencyResolver resolver = new SiblingDependencyResolver();
|
||||
|
||||
// When we resolve from A, it should transitively find B and C
|
||||
Set<Path> siblings = resolver.resolveSiblings(moduleA);
|
||||
|
||||
assertThat(siblings).containsExactlyInAnyOrder(moduleB.normalize(), moduleC.normalize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveMavenSiblingsTransitively(@TempDir Path tempDir) throws IOException {
|
||||
// Create root project
|
||||
Path rootDir = tempDir.resolve("my-maven-root");
|
||||
Files.createDirectories(rootDir);
|
||||
Files.writeString(rootDir.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>root</artifactId></project>");
|
||||
// Create a dummy .git so findMavenRoot knows where to stop
|
||||
Files.createDirectories(rootDir.resolve(".git"));
|
||||
|
||||
// Create module A (depends on B)
|
||||
Path moduleA = rootDir.resolve("module-a");
|
||||
Files.createDirectories(moduleA);
|
||||
Files.writeString(moduleA.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>module-a</artifactId><dependencies><dependency><groupId>com.acme</groupId><artifactId>module-b</artifactId><version>1.0</version></dependency></dependencies></project>");
|
||||
|
||||
// Create module B (depends on C)
|
||||
Path moduleB = rootDir.resolve("module-b");
|
||||
Files.createDirectories(moduleB);
|
||||
Files.writeString(moduleB.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>module-b</artifactId><dependencies><dependency><groupId>com.acme</groupId><artifactId>module-c</artifactId><version>1.0</version></dependency></dependencies></project>");
|
||||
|
||||
// Create module C (no deps)
|
||||
Path moduleC = rootDir.resolve("module-c");
|
||||
Files.createDirectories(moduleC);
|
||||
Files.writeString(moduleC.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>module-c</artifactId></project>");
|
||||
|
||||
SiblingDependencyResolver resolver = new SiblingDependencyResolver();
|
||||
|
||||
// When we resolve from A, it should transitively find B and C and the parent rootDir
|
||||
Set<Path> siblings = resolver.resolveSiblings(moduleA);
|
||||
|
||||
assertThat(siblings).containsExactlyInAnyOrder(moduleB.normalize(), moduleC.normalize(), rootDir.normalize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveMavenModulesFromAggregator(@TempDir Path tempDir) throws IOException {
|
||||
// Create root project
|
||||
Path rootDir = tempDir.resolve("my-aggregator-root");
|
||||
Files.createDirectories(rootDir);
|
||||
Files.writeString(rootDir.resolve("pom.xml"), "<project><groupId>com.acme</groupId><artifactId>root</artifactId><modules><module>service-x</module><module>service-y</module></modules></project>");
|
||||
Files.createDirectories(rootDir.resolve(".git"));
|
||||
|
||||
// Create service X
|
||||
Path serviceX = rootDir.resolve("service-x");
|
||||
Files.createDirectories(serviceX);
|
||||
Files.writeString(serviceX.resolve("pom.xml"), "<project><artifactId>service-x</artifactId></project>");
|
||||
|
||||
// Create service Y
|
||||
Path serviceY = rootDir.resolve("service-y");
|
||||
Files.createDirectories(serviceY);
|
||||
Files.writeString(serviceY.resolve("pom.xml"), "<project><artifactId>service-y</artifactId></project>");
|
||||
|
||||
SiblingDependencyResolver resolver = new SiblingDependencyResolver();
|
||||
|
||||
// Resolving from rootDir should pick up the <module> tags and resolve service-x and service-y
|
||||
Set<Path> siblings = resolver.resolveSiblings(rootDir);
|
||||
|
||||
assertThat(siblings).containsExactlyInAnyOrder(serviceX.normalize(), serviceY.normalize());
|
||||
}
|
||||
}
|
||||
@@ -66,4 +66,59 @@ class BuilderLocalVariableTest {
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("OrderEvents.PAY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTraceFluentBuilder(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class MyController {
|
||||
private MyService service;
|
||||
public void processEvent() {
|
||||
MyEvent event = MyEvent.builder().type(MyEnum.STATE_2).build();
|
||||
service.process(event.getType());
|
||||
}
|
||||
}
|
||||
|
||||
class MyService {
|
||||
public void process(MyEnum event) {}
|
||||
}
|
||||
|
||||
class MyEvent {
|
||||
private MyEnum type;
|
||||
public static Builder builder() { return new Builder(); }
|
||||
public MyEnum getType() { return type; }
|
||||
public static class Builder {
|
||||
private MyEnum type;
|
||||
public Builder type(MyEnum type) { this.type = type; return this; }
|
||||
public MyEvent build() { return new MyEvent(); }
|
||||
}
|
||||
}
|
||||
|
||||
enum MyEnum { STATE_1, STATE_2 }
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("MyConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.MyController")
|
||||
.methodName("processEvent")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.MyService")
|
||||
.methodName("process")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
CallChain chain = chains.get(0);
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("MyEnum.STATE_2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CallGraphPathFinderTest {
|
||||
|
||||
private CallGraphPathFinder pathFinder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
pathFinder = new CallGraphPathFinder(new CodebaseContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipFrameworkAndJdkPaths() {
|
||||
Map<String, List<CallEdge>> graph = new HashMap<>();
|
||||
|
||||
// Setup an anonymized call graph where business logic delegates to a platform boundary
|
||||
// e.g. BusinessService.process -> java.util.stream.Stream.map -> AnotherBusinessService.doWork
|
||||
// The pathfinder should skip the java.* path, avoiding spurious hops.
|
||||
|
||||
graph.put("com.acme.BusinessService.process", List.of(
|
||||
new CallEdge("java.util.stream.Stream.map", List.of()),
|
||||
new CallEdge("com.acme.DirectService.execute", List.of())
|
||||
));
|
||||
graph.put("java.util.stream.Stream.map", List.of(
|
||||
new CallEdge("com.acme.AnotherBusinessService.doWork", List.of())
|
||||
));
|
||||
graph.put("com.acme.DirectService.execute", List.of(
|
||||
new CallEdge("com.acme.AnotherBusinessService.doWork", List.of())
|
||||
));
|
||||
|
||||
List<List<String>> paths = pathFinder.findAllPaths(
|
||||
"com.acme.BusinessService.process",
|
||||
"com.acme.AnotherBusinessService.doWork",
|
||||
graph,
|
||||
new HashSet<>()
|
||||
);
|
||||
|
||||
// It should ONLY find the direct business path, skipping the java.util path.
|
||||
assertThat(paths).hasSize(1);
|
||||
assertThat(paths.get(0)).containsExactly(
|
||||
"com.acme.BusinessService.process",
|
||||
"com.acme.DirectService.execute",
|
||||
"com.acme.AnotherBusinessService.doWork"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFollowValidBusinessPaths() {
|
||||
Map<String, List<CallEdge>> graph = new HashMap<>();
|
||||
|
||||
graph.put("com.acme.Start.run", List.of(
|
||||
new CallEdge("com.acme.Middle.process", List.of())
|
||||
));
|
||||
graph.put("com.acme.Middle.process", List.of(
|
||||
new CallEdge("com.acme.End.finish", List.of())
|
||||
));
|
||||
|
||||
List<List<String>> paths = pathFinder.findAllPaths(
|
||||
"com.acme.Start.run",
|
||||
"com.acme.End.finish",
|
||||
graph,
|
||||
new HashSet<>()
|
||||
);
|
||||
|
||||
assertThat(paths).hasSize(1);
|
||||
assertThat(paths.get(0)).containsExactly(
|
||||
"com.acme.Start.run",
|
||||
"com.acme.Middle.process",
|
||||
"com.acme.End.finish"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class DynamicClasspathResolverTest {
|
||||
.filter(arg -> arg.startsWith("-Dmdep.outputFile="))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
Path outputFile = Path.of(outputFileArg.substring("-Dmdep.outputFile=".length()));
|
||||
Path outputFile = pb.directory().toPath().resolve(outputFileArg.substring("-Dmdep.outputFile=".length()));
|
||||
|
||||
Files.writeString(outputFile, cp);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class EnterpriseBugsTest {
|
||||
|
||||
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||
@@ -168,4 +169,108 @@ class EnterpriseBugsTest {
|
||||
.build();
|
||||
assertThat(engine.isRoutedToCorrectMachine(chain2, "com.example.OrderStateMachineConfig", context)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindPathsUsingEagerEntryPointExpansion(@TempDir Path tempDir) throws IOException {
|
||||
String apiSrc = """
|
||||
package com.example;
|
||||
public interface OrderApi {
|
||||
void submit();
|
||||
}
|
||||
""";
|
||||
String controllerSrc = """
|
||||
package com.example;
|
||||
public class OrderController implements OrderApi {
|
||||
private OrderService service;
|
||||
public void submit() {
|
||||
service.trigger("someEvent");
|
||||
}
|
||||
}
|
||||
""";
|
||||
String serviceSrc = """
|
||||
package com.example;
|
||||
public class OrderService {
|
||||
public void trigger(String EVENT) {}
|
||||
}
|
||||
""";
|
||||
|
||||
Files.writeString(tempDir.resolve("OrderApi.java"), apiSrc);
|
||||
Files.writeString(tempDir.resolve("OrderController.java"), controllerSrc);
|
||||
Files.writeString(tempDir.resolve("OrderService.java"), serviceSrc);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.OrderApi")
|
||||
.methodName("submit")
|
||||
.build();
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.className("com.example.OrderService")
|
||||
.methodName("trigger")
|
||||
.event("EVENT")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(triggerPoint));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
assertThat(chains.get(0).getMethodChain()).contains(
|
||||
"com.example.OrderController.submit"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotPolymorphicallyExpandExternalInterfacesWithManyImpls(@TempDir Path tempDir) throws IOException {
|
||||
String impl1 = "package com.example; public class R1 implements java.lang.Runnable { public void run() {} }";
|
||||
String impl2 = "package com.example; public class R2 implements java.lang.Runnable { public void run() {} }";
|
||||
String impl3 = "package com.example; public class R3 implements java.lang.Runnable { public void run() {} }";
|
||||
String impl4 = "package com.example; public class R4 implements java.lang.Runnable { public void run() {} }";
|
||||
String executorSrc = """
|
||||
package com.example;
|
||||
public class ExecutorService {
|
||||
public void execute(java.lang.Runnable task) {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
Files.writeString(tempDir.resolve("R1.java"), impl1);
|
||||
Files.writeString(tempDir.resolve("R2.java"), impl2);
|
||||
Files.writeString(tempDir.resolve("R3.java"), impl3);
|
||||
Files.writeString(tempDir.resolve("R4.java"), impl4);
|
||||
Files.writeString(tempDir.resolve("ExecutorService.java"), executorSrc);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||
var graph = engine.buildCallGraph();
|
||||
|
||||
// The execute method calls task.run(). Since java.lang.Runnable is external (td == null) and has > 3 impls,
|
||||
// it should NOT resolve task.run() to R1.run(), R2.run(), etc.
|
||||
var edges = graph.get("com.example.ExecutorService.execute");
|
||||
if (edges != null) {
|
||||
for (var edge : edges) {
|
||||
assertThat(edge.getTargetMethod()).isNotEqualTo("com.example.R1.run");
|
||||
}
|
||||
assertThat(edges).extracting("targetMethod").containsExactly("java.lang.Runnable.run");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotTreatValueOfExpressionAsWildcardMatchingAllTransitions() {
|
||||
click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine engine =
|
||||
new click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine();
|
||||
|
||||
click.kamil.springstatemachineexporter.model.Event smEvent =
|
||||
click.kamil.springstatemachineexporter.model.Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||
|
||||
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||
.event("OrderEvents.valueOf(someVar)")
|
||||
.build();
|
||||
|
||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,4 +1036,47 @@ class HeuristicCallGraphEngineCoreTest {
|
||||
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("REACTIVE_EVENT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractEventFromSwitchExpressionAndParentheses(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class SwitchOrderService {
|
||||
public void processOrder(int type) {
|
||||
String e = (((switch(type) {
|
||||
case 1 -> "PAY";
|
||||
default -> { yield "CANCEL"; }
|
||||
})));
|
||||
sendEvent(e);
|
||||
}
|
||||
|
||||
public void sendEvent(String event) {
|
||||
}
|
||||
}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("SwitchOrderService.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.SwitchOrderService")
|
||||
.methodName("processOrder")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.SwitchOrderService")
|
||||
.methodName("sendEvent")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
System.out.println("POLY EVENTS: " + chains.get(0).getTriggerPoint().getPolymorphicEvents());
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("PAY", "CANCEL");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ class HeuristicCallGraphEngineExtendedTest {
|
||||
java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||
|
||||
@@ -954,4 +954,42 @@ class HeuristicCallGraphEngineGetterTest {
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("SMEvent.PAY_RECEIVED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveGetterOnInterProceduralClassInstanceCreation() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class OrderController {
|
||||
private OrderService service;
|
||||
public void handle() {
|
||||
service.process(new DomainEvent("PAYLOAD_ID"));
|
||||
}
|
||||
}
|
||||
|
||||
class DomainEvent {
|
||||
private String id;
|
||||
public DomainEvent(String id) { this.id = id; }
|
||||
public String getEvent() { return "INLINE_EVENT"; }
|
||||
}
|
||||
|
||||
class OrderService {
|
||||
public void process(DomainEvent event) {
|
||||
sendEvent(event.getEvent());
|
||||
}
|
||||
public void sendEvent(String ev) {}
|
||||
}
|
||||
""";
|
||||
Path tempDir = Files.createTempDirectory("callgraph_interproc_cic");
|
||||
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handle").build();
|
||||
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("sendEvent").event("ev").build();
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,6 +313,186 @@ class HeuristicCallGraphEnginePolymorphicTest {
|
||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("POST_ACCEPTANCE_REJECTED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePolymorphicEventsThroughAssertSupportedMethod() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class Dispatcher {
|
||||
private HandlerService service;
|
||||
public void dispatchRequest(Payload domainEvent) {
|
||||
doDispatch(domainEvent);
|
||||
}
|
||||
|
||||
private void doDispatch(Payload domainEvent) {
|
||||
service.handleEvent(checkAndMapEvent(domainEvent.getType()));
|
||||
}
|
||||
|
||||
private CustomEvents checkAndMapEvent(CustomEvents e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
class HandlerService {
|
||||
public void handleEvent(CustomEvents event) {}
|
||||
}
|
||||
|
||||
interface Payload {
|
||||
CustomEvents getType();
|
||||
}
|
||||
|
||||
class AlphaPayload implements Payload {
|
||||
public CustomEvents getType() { return CustomEvents.ALPHA; }
|
||||
}
|
||||
|
||||
class BetaPayload implements Payload {
|
||||
public CustomEvents getType() { return CustomEvents.BETA; }
|
||||
}
|
||||
|
||||
enum CustomEvents { ALPHA, BETA }
|
||||
""";
|
||||
Path tempDir = Files.createTempDirectory("callgraph_test_poly_assert");
|
||||
Files.writeString(tempDir.resolve("DispatcherConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.Dispatcher")
|
||||
.methodName("dispatchRequest")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.HandlerService")
|
||||
.methodName("handleEvent")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
CallChain chain = chains.get(0);
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("CustomEvents.ALPHA", "CustomEvents.BETA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePolymorphicEventsThroughNestedConverters() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class Dispatcher {
|
||||
private HandlerService service;
|
||||
public void dispatchRequest(Payload domainEvent) {
|
||||
doDispatch(domainEvent);
|
||||
}
|
||||
|
||||
private void doDispatch(Payload domainEvent) {
|
||||
// NESTED CONVERTERS
|
||||
service.handleEvent(verify(sanitize(domainEvent.type())));
|
||||
}
|
||||
|
||||
private CustomEvents sanitize(CustomEvents e) { return e; }
|
||||
private CustomEvents verify(CustomEvents e) { return e; }
|
||||
}
|
||||
|
||||
class HandlerService {
|
||||
public void handleEvent(CustomEvents event) {}
|
||||
}
|
||||
|
||||
interface Payload {
|
||||
CustomEvents type();
|
||||
}
|
||||
|
||||
class AlphaPayload implements Payload {
|
||||
public CustomEvents type() { return CustomEvents.ALPHA; }
|
||||
}
|
||||
|
||||
enum CustomEvents { ALPHA, BETA }
|
||||
""";
|
||||
Path tempDir = Files.createTempDirectory("callgraph_test_poly_nested");
|
||||
Files.writeString(tempDir.resolve("DispatcherConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.Dispatcher")
|
||||
.methodName("dispatchRequest")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.HandlerService")
|
||||
.methodName("handleEvent")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
System.out.println("DEBUG context.getImplementations(Payload) = " + context.getImplementations("Payload"));
|
||||
System.out.println("DEBUG context.getImplementations(com.example.Payload) = " + context.getImplementations("com.example.Payload"));
|
||||
System.out.println("DEBUG getPolymorphicEvents() = " + chains.get(0).getTriggerPoint().getPolymorphicEvents());
|
||||
assertThat(chains).hasSize(1);
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactly("CustomEvents.ALPHA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolvePolymorphicEventsWithCustomGetterSuffixes() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class Dispatcher {
|
||||
private HandlerService service;
|
||||
public void dispatchRequest(Payload domainEvent) {
|
||||
doDispatch(domainEvent);
|
||||
}
|
||||
|
||||
private void doDispatch(Payload domainEvent) {
|
||||
// Using .event() instead of .getEvent()
|
||||
service.handleEvent(check(domainEvent.event()));
|
||||
}
|
||||
|
||||
private CustomEvents check(CustomEvents e) { return e; }
|
||||
}
|
||||
|
||||
class HandlerService {
|
||||
public void handleEvent(CustomEvents event) {}
|
||||
}
|
||||
|
||||
interface Payload {
|
||||
CustomEvents event();
|
||||
}
|
||||
|
||||
class BetaPayload implements Payload {
|
||||
public CustomEvents event() { return CustomEvents.BETA; }
|
||||
}
|
||||
|
||||
enum CustomEvents { ALPHA, BETA }
|
||||
""";
|
||||
Path tempDir = Files.createTempDirectory("callgraph_test_poly_suffix");
|
||||
Files.writeString(tempDir.resolve("DispatcherConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.Dispatcher")
|
||||
.methodName("dispatchRequest")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.HandlerService")
|
||||
.methodName("handleEvent")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
assertThat(chains).hasSize(1);
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactly("CustomEvents.BETA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveMappedTransitionEnumThroughAbstractBaseClassAndMultipleArgs() throws IOException {
|
||||
String source = """
|
||||
|
||||
@@ -466,7 +466,75 @@ class HeuristicCallGraphEngineTypeTest {
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
CallChain chain = chains.get(0);
|
||||
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("mapToEnum(str)");
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("MyEvents.A", "MyEvents.B");
|
||||
.containsExactly("<SYMBOLIC: com.example.MyEvents.*>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveTernaryValueOf(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class Dispatcher {
|
||||
public void dispatch(String type, String eventStr) {
|
||||
StateMachine sm = new StateMachine();
|
||||
if ("ORDER".equals(type)) {
|
||||
OrderEvent ev = OrderEvent.valueOf(eventStr);
|
||||
sm.sendEvent(ev);
|
||||
} else {
|
||||
UserEvent ev = UserEvent.valueOf(eventStr);
|
||||
sm.sendEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
enum OrderEvent { PAY }
|
||||
enum UserEvent { VERIFY }
|
||||
class StateMachine {
|
||||
public void sendEvent(Object e) {}
|
||||
}
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("Dispatcher.java"), source);
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entry = EntryPoint.builder()
|
||||
.className("com.example.Dispatcher")
|
||||
.methodName("dispatch")
|
||||
.build();
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.StateMachine")
|
||||
.methodName("sendEvent")
|
||||
.event("e")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entry), List.of(trigger));
|
||||
assertThat(chains).hasSize(1);
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: UserEvent.*>");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnterpriseDispatcherResolution() throws IOException {
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(Path.of("../state_machines/state_machine_enterprise"));
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entry = EntryPoint.builder()
|
||||
.className("click.kamil.enterprise.web.StateMachineController")
|
||||
.methodName("transition")
|
||||
.build();
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("click.kamil.enterprise.web.StateMachineDispatcher")
|
||||
.methodName("dispatch")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entry), List.of(trigger));
|
||||
assertThat(chains).isNotEmpty();
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,4 +282,48 @@ class JdtCallGraphEngineIntegrationTest {
|
||||
"click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnterpriseDispatcherResolutionJdt() throws IOException {
|
||||
Path projectRoot = Path.of("../state_machines/state_machine_enterprise").toAbsolutePath().normalize();
|
||||
|
||||
CodebaseContext ctx = new CodebaseContext();
|
||||
ctx.setProjectRoot(projectRoot);
|
||||
ctx.setSourcepath(List.of(projectRoot.resolve("src/main/java").toString()));
|
||||
|
||||
DynamicClasspathResolver resolver = new DynamicClasspathResolver();
|
||||
List<String> cp = resolver.resolveClasspath(projectRoot);
|
||||
if (!cp.isEmpty()) {
|
||||
ctx.setClasspath(cp);
|
||||
}
|
||||
ctx.setResolveBindings(true);
|
||||
ctx.scan(Set.of(projectRoot), Collections.emptySet());
|
||||
|
||||
SpringBeanRegistry reg = new SpringBeanRegistry();
|
||||
SpringContextScanner scanner = new SpringContextScanner(reg);
|
||||
|
||||
for (var cu : ctx.getCompilationUnits()) {
|
||||
cu.accept(scanner);
|
||||
}
|
||||
|
||||
SpringDependencyResolver dependencyResolver = new SpringDependencyResolver(reg);
|
||||
InjectionPointAnalyzer injectionAnalyzer = new InjectionPointAnalyzer(dependencyResolver);
|
||||
|
||||
JdtCallGraphEngine jdtEngine = new JdtCallGraphEngine(ctx, injectionAnalyzer);
|
||||
|
||||
EntryPoint entry = EntryPoint.builder()
|
||||
.className("click.kamil.enterprise.web.StateMachineController")
|
||||
.methodName("transition")
|
||||
.build();
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("click.kamil.enterprise.web.StateMachineDispatcher")
|
||||
.methodName("dispatch")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = jdtEngine.findChains(List.of(entry), List.of(trigger));
|
||||
assertThat(chains).isNotEmpty();
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,4 +173,111 @@ class LocalVariableTest {
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("OrderEvents.CANCEL");
|
||||
}
|
||||
@Test
|
||||
void shouldUnwrapExplicitPassthroughMethod(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class OrderController {
|
||||
private OrderService service;
|
||||
private EventValidator validator;
|
||||
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||
OrderEvents resolved = validator.passthrough(domainEvent.getType());
|
||||
service.updateOrderState(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
class EventValidator {
|
||||
public OrderEvents passthrough(OrderEvents e) {
|
||||
if (e == null) throw new IllegalArgumentException();
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
class OrderService {
|
||||
public void updateOrderState(OrderEvents event) {}
|
||||
}
|
||||
|
||||
class RichOrderEvent {
|
||||
public OrderEvents getType() {
|
||||
return OrderEvents.PAY;
|
||||
}
|
||||
}
|
||||
|
||||
enum OrderEvents { PAY, CANCEL }
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.OrderController")
|
||||
.methodName("processOrderEvent")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.OrderService")
|
||||
.methodName("updateOrderState")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
CallChain chain = chains.get(0);
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("OrderEvents.PAY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallbackToPrefixMatchingForExternalMethods(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class OrderController {
|
||||
private OrderService service;
|
||||
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||
OrderEvents resolved = UnknownExternalClass.validate(domainEvent.getType());
|
||||
service.updateOrderState(resolved);
|
||||
}
|
||||
}
|
||||
|
||||
class OrderService {
|
||||
public void updateOrderState(OrderEvents event) {}
|
||||
}
|
||||
|
||||
class RichOrderEvent {
|
||||
public OrderEvents getType() {
|
||||
return OrderEvents.PAY;
|
||||
}
|
||||
}
|
||||
|
||||
enum OrderEvents { PAY, CANCEL }
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.OrderController")
|
||||
.methodName("processOrderEvent")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.OrderService")
|
||||
.methodName("updateOrderState")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
CallChain chain = chains.get(0);
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.doesNotContain("OrderEvents.PAY");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +92,153 @@ class PolymorphicDispatchCallGraphTest {
|
||||
"com.example.TransportOrderService.processOrderEvent"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFilterPolymorphicDispatchContextually(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
|
||||
public class LogisticsController {
|
||||
private LogisticsService service;
|
||||
|
||||
public void handle(String event) {
|
||||
service.process(event);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractOrderService {
|
||||
public void process(String event) {
|
||||
String resolved = assertSupportedOrderEvent(event);
|
||||
sendEvent(resolved);
|
||||
}
|
||||
|
||||
protected abstract String assertSupportedOrderEvent(String event);
|
||||
|
||||
public void sendEvent(String ev) {}
|
||||
}
|
||||
|
||||
class LogisticsService extends AbstractOrderService {
|
||||
@Override
|
||||
protected String assertSupportedOrderEvent(String event) {
|
||||
return LogisticsEvent.valueOf(event).name();
|
||||
}
|
||||
}
|
||||
|
||||
class ComputerStoreService extends AbstractOrderService {
|
||||
@Override
|
||||
protected String assertSupportedOrderEvent(String event) {
|
||||
return ComputerEvent.valueOf(event).name();
|
||||
}
|
||||
}
|
||||
|
||||
enum LogisticsEvent { DELIVERED, RETURNED }
|
||||
enum ComputerEvent { SHIPPED, REFUNDED }
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.LogisticsController")
|
||||
.methodName("handle")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.AbstractOrderService")
|
||||
.methodName("sendEvent")
|
||||
.event("ev")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("LogisticsEvent.DELIVERED", "LogisticsEvent.RETURNED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveMapBasedDynamicDispatch(@TempDir Path tempDir) throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class DispatcherController {
|
||||
private Map<String, AbstractService> serviceMap = new HashMap<>();
|
||||
|
||||
public DispatcherController(LogisticsService logistics, ComputerStoreService computerStore) {
|
||||
serviceMap.put("LOGISTICS", logistics);
|
||||
serviceMap.put("COMPUTER", computerStore);
|
||||
}
|
||||
|
||||
public void handle(String machineType, String event) {
|
||||
serviceMap.get(machineType).process(event);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractService {
|
||||
public void process(String event) {
|
||||
String resolved = assertSupportedEvent(event);
|
||||
sendEvent(resolved);
|
||||
}
|
||||
protected abstract String assertSupportedEvent(String event);
|
||||
public void sendEvent(String ev) {}
|
||||
}
|
||||
|
||||
class LogisticsService extends AbstractService {
|
||||
@Override
|
||||
protected String assertSupportedEvent(String event) {
|
||||
return LogisticsEvent.valueOf(event).name();
|
||||
}
|
||||
}
|
||||
|
||||
class ComputerStoreService extends AbstractService {
|
||||
@Override
|
||||
protected String assertSupportedEvent(String event) {
|
||||
return ComputerEvent.valueOf(event).name();
|
||||
}
|
||||
}
|
||||
|
||||
enum LogisticsEvent { DELIVERED, RETURNED }
|
||||
enum ComputerEvent { SHIPPED, REFUNDED }
|
||||
""";
|
||||
Files.writeString(tempDir.resolve("DispatchConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.DispatcherController")
|
||||
.methodName("handle")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.AbstractService")
|
||||
.methodName("sendEvent")
|
||||
.event("ev")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(2);
|
||||
|
||||
CallChain chainLogistics = chains.stream()
|
||||
.filter(c -> "machineType == \"LOGISTICS\"".equals(c.getTriggerPoint().getConstraint()))
|
||||
.findFirst().orElseThrow();
|
||||
assertThat(chainLogistics.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("LogisticsEvent.DELIVERED", "LogisticsEvent.RETURNED");
|
||||
|
||||
CallChain chainComputer = chains.stream()
|
||||
.filter(c -> "machineType == \"COMPUTER\"".equals(c.getTriggerPoint().getConstraint()))
|
||||
.findFirst().orElseThrow();
|
||||
assertThat(chainComputer.getTriggerPoint().getPolymorphicEvents())
|
||||
.containsExactlyInAnyOrder("ComputerEvent.SHIPPED", "ComputerEvent.REFUNDED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.service;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class VariableTracerTest {
|
||||
|
||||
@Test
|
||||
void shouldShortCircuitLiteralStringResolution() {
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
VariableTracer tracer = new VariableTracer(context, new ConstantResolver());
|
||||
|
||||
// Even with an empty context and call graph, passing a raw String literal should return instantly
|
||||
// without trying to recursively traverse the path or crash.
|
||||
String resolved = tracer.resolveArgumentValue("\"PAY_EVENT\"", "com.example.Order.process", List.of("com.example.Order.process"), 0, Map.of());
|
||||
|
||||
assertThat(resolved).isEqualTo("\"PAY_EVENT\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBridgePolymorphicParametersPositionally(@TempDir Path tempDir) throws IOException {
|
||||
Path comFoo = tempDir.resolve("com/foo");
|
||||
Files.createDirectories(comFoo);
|
||||
|
||||
// Setup an interface and implementation with DIFFERENT parameter names
|
||||
Files.writeString(comFoo.resolve("PaymentService.java"),
|
||||
"package com.foo;\n" +
|
||||
"public interface PaymentService {\n" +
|
||||
" void submit(String eventName);\n" +
|
||||
"}"
|
||||
);
|
||||
Files.writeString(comFoo.resolve("PaymentServiceImpl.java"),
|
||||
"package com.foo;\n" +
|
||||
"public class PaymentServiceImpl implements PaymentService {\n" +
|
||||
" @Override\n" +
|
||||
" public void submit(String e) {}\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
VariableTracer tracer = new VariableTracer(context, new ConstantResolver());
|
||||
|
||||
// Simulate tracing from the Interface backwards to the Implementation.
|
||||
// Because this is a polymorphic bridge, there is NO direct call edge in the AST call graph.
|
||||
Map<String, String> params = tracer.buildParameterValuesMap(
|
||||
"com.foo.PaymentService.submit",
|
||||
"com.foo.PaymentServiceImpl.submit",
|
||||
Map.of(), // Empty call graph (no direct edges)
|
||||
List.of("com.foo.PaymentService.submit", "com.foo.PaymentServiceImpl.submit"),
|
||||
0
|
||||
);
|
||||
|
||||
// The tracer should have scanned both ASTs and mapped parameter 0 -> parameter 0
|
||||
assertThat(params).hasSize(1);
|
||||
assertThat(params).containsEntry("e", "eventName");
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ class AstTransitionParserTest {
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getGuard())
|
||||
assertThat(transition.getGuards().getFirst())
|
||||
.extracting(Guard::expression)
|
||||
.isEqualTo("amount > 0");
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class AstTransitionParserTest {
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
Transition transition = transitions.getFirst();
|
||||
assertThat(transition.getGuard().isLambda()).isTrue();
|
||||
assertThat(transition.getGuards().getFirst().isLambda()).isTrue();
|
||||
assertThat(transition.getActions())
|
||||
.extracting(Action::isLambda)
|
||||
.containsExactly(true);
|
||||
@@ -230,7 +230,7 @@ class AstTransitionParserTest {
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||
assertThat(transitions.getFirst().getGuards().getFirst().internalLogic())
|
||||
.contains("context.getMessage() != null");
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ class AstTransitionParserTest {
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||
assertThat(transitions.getFirst().getGuards().getFirst().internalLogic())
|
||||
.contains("return expected.equals(context.getMessage());")
|
||||
.doesNotContain("protected Guard<String, String> guardVarEquals");
|
||||
}
|
||||
@@ -364,7 +364,7 @@ class AstTransitionParserTest {
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||
assertThat(transitions.getFirst().getGuards().getFirst().internalLogic())
|
||||
.contains("return \"OK\".equals(context.getMessage().getPayload());")
|
||||
.contains("public boolean evaluate");
|
||||
}
|
||||
@@ -390,7 +390,7 @@ class AstTransitionParserTest {
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||
|
||||
assertThat(transitions).hasSize(1);
|
||||
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||
assertThat(transitions.getFirst().getGuards().getFirst().internalLogic())
|
||||
.contains("context -> \"LOCAL_OK\".equals(context.getMessage())");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class PlantUmlTest {
|
||||
t.setTargetStates(List.of(state2));
|
||||
|
||||
// Add a guard and action containing problematic characters
|
||||
t.setGuard(Guard.of("list.size() < 5", false, null, null, null, null));
|
||||
t.getGuards().add(Guard.of("list.size() < 5", false, null, null, null, null));
|
||||
t.setActions(List.of(Action.of("doSomething(new int[]{1, 2})", false, null, null, null, null)));
|
||||
|
||||
String result = plantUml.export(
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.EVENT1",
|
||||
"fullIdentifier" : "Events.EVENT1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "Events.EVENT2",
|
||||
"fullIdentifier" : "Events.EVENT2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.EVENT3",
|
||||
"fullIdentifier" : "Events.EVENT3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.EVENT4",
|
||||
"fullIdentifier" : "Events.EVENT4"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"rawName" : "Events.EVENT5",
|
||||
"fullIdentifier" : "Events.EVENT5"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.EVENT6",
|
||||
"fullIdentifier" : "Events.EVENT6"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -128,7 +128,7 @@
|
||||
"rawName" : "Events.EVENT7",
|
||||
"fullIdentifier" : "Events.EVENT7"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -145,7 +145,7 @@
|
||||
"rawName" : "Events.EVENT8",
|
||||
"fullIdentifier" : "Events.EVENT8"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -162,7 +162,7 @@
|
||||
"rawName" : "Events.EVENT9",
|
||||
"fullIdentifier" : "Events.EVENT9"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -179,7 +179,7 @@
|
||||
"rawName" : "Events.EVENT10",
|
||||
"fullIdentifier" : "Events.EVENT10"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -196,7 +196,7 @@
|
||||
"rawName" : "Events.EVENT11",
|
||||
"fullIdentifier" : "Events.EVENT11"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -213,7 +213,7 @@
|
||||
"rawName" : "Events.EVENT12",
|
||||
"fullIdentifier" : "Events.EVENT12"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -230,7 +230,7 @@
|
||||
"rawName" : "Events.EVENT13",
|
||||
"fullIdentifier" : "Events.EVENT13"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -247,7 +247,7 @@
|
||||
"rawName" : "Events.EVENT14",
|
||||
"fullIdentifier" : "Events.EVENT14"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -264,7 +264,7 @@
|
||||
"rawName" : "Events.EVENT15",
|
||||
"fullIdentifier" : "Events.EVENT15"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -278,14 +278,14 @@
|
||||
"fullIdentifier" : "States.STATE17"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 86,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -299,14 +299,14 @@
|
||||
"fullIdentifier" : "States.STATE18"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value2\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 87,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -320,7 +320,7 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -334,14 +334,14 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header1\",\"foo\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 92,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -355,7 +355,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -369,14 +369,14 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value3\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 97,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -390,7 +390,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -404,14 +404,14 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"reset\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 102,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -425,7 +425,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -439,14 +439,14 @@
|
||||
"fullIdentifier" : "States.STATE5"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header2\",\"bar\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 107,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -460,7 +460,7 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -474,14 +474,14 @@
|
||||
"fullIdentifier" : "States.STATE13"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo13\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 112,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -495,14 +495,14 @@
|
||||
"fullIdentifier" : "States.STATE14"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo14\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 113,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -516,7 +516,7 @@
|
||||
"fullIdentifier" : "States.STATE15"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -530,14 +530,14 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goBack10\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 118,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -551,7 +551,7 @@
|
||||
"fullIdentifier" : "States.STATE11"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -565,14 +565,14 @@
|
||||
"fullIdentifier" : "States.STATE12"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"loop12\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 123,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -586,7 +586,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -600,14 +600,14 @@
|
||||
"fullIdentifier" : "States.STATE8"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBack\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 128,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -621,14 +621,14 @@
|
||||
"fullIdentifier" : "States.STATE7"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBackMore\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 129,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -642,7 +642,7 @@
|
||||
"fullIdentifier" : "States.STATE6"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -656,14 +656,14 @@
|
||||
"fullIdentifier" : "States.STATE9"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"forward9\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 134,
|
||||
"className" : "click.kamil.examples.statemachine.complex.ComplexStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/complex/ComplexStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -677,7 +677,7 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
} ],
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
digraph statemachine {
|
||||
rankdir=LR;
|
||||
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
|
||||
edge [fontname="Arial", fontsize=10];
|
||||
|
||||
_start [shape=circle, label="", fillcolor=black, width=0.1];
|
||||
_start -> DRAFT;
|
||||
APPROVED [fillcolor=lightgray];
|
||||
DRAFT -> IN_REVIEW [label="DocumentEvent.SUBMIT", style="solid", color="black"];
|
||||
IN_REVIEW -> APPROVED [label="DocumentEvent.APPROVE", style="solid", color="black"];
|
||||
IN_REVIEW -> DRAFT [label="DocumentEvent.REJECT", style="solid", color="black"];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,700 @@
|
||||
{
|
||||
"metadata" : {
|
||||
"triggers" : [ {
|
||||
"event" : "OrderEvent.PAY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 30,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "OrderEvent.SHIP",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 37,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "DocumentEvent.SUBMIT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 44,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "DocumentEvent.APPROVE",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 51,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "DocumentEvent.REJECT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 58,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "UserEvent.VERIFY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 65,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "UserEvent.SUSPEND",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 72,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "ORDER",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "DOCUMENT",
|
||||
"lineNumber" : 87,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "USER",
|
||||
"lineNumber" : 93,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/pay",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/ship",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/ship",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/submit",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/approve",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/approve",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/reject",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/reject",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/verify",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/verify",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/suspend",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/suspend",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
} ],
|
||||
"callChains" : [ {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/pay",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.payOrder", "click.kamil.enterprise.web.StateMachineDispatcher.payOrder" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "OrderEvent.PAY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 30,
|
||||
"polymorphicEvents" : [ "OrderEvent.PAY" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/ship",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/ship",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.shipOrder", "click.kamil.enterprise.web.StateMachineDispatcher.shipOrder" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "OrderEvent.SHIP",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 37,
|
||||
"polymorphicEvents" : [ "OrderEvent.SHIP" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/submit",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.submitDocument", "click.kamil.enterprise.web.StateMachineDispatcher.submitDocument" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "DocumentEvent.SUBMIT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 44,
|
||||
"polymorphicEvents" : [ "DocumentEvent.SUBMIT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "DocumentState.DRAFT",
|
||||
"targetState" : "DocumentState.IN_REVIEW",
|
||||
"event" : "DocumentEvent.SUBMIT"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/approve",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/approve",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.approveDocument", "click.kamil.enterprise.web.StateMachineDispatcher.approveDocument" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "DocumentEvent.APPROVE",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 51,
|
||||
"polymorphicEvents" : [ "DocumentEvent.APPROVE" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "DocumentState.IN_REVIEW",
|
||||
"targetState" : "DocumentState.APPROVED",
|
||||
"event" : "DocumentEvent.APPROVE"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/reject",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/reject",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.rejectDocument", "click.kamil.enterprise.web.StateMachineDispatcher.rejectDocument" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "DocumentEvent.REJECT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 58,
|
||||
"polymorphicEvents" : [ "DocumentEvent.REJECT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "DocumentState.IN_REVIEW",
|
||||
"targetState" : "DocumentState.DRAFT",
|
||||
"event" : "DocumentEvent.REJECT"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/verify",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/verify",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.verifyUser", "click.kamil.enterprise.web.StateMachineDispatcher.verifyUser" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "UserEvent.VERIFY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 65,
|
||||
"polymorphicEvents" : [ "UserEvent.VERIFY" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/suspend",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/suspend",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.suspendUser", "click.kamil.enterprise.web.StateMachineDispatcher.suspendUser" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "UserEvent.SUSPEND",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 72,
|
||||
"polymorphicEvents" : [ "UserEvent.SUSPEND" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "ORDER",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
||||
"external" : true,
|
||||
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "DOCUMENT",
|
||||
"lineNumber" : 87,
|
||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
||||
"external" : true,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "USER",
|
||||
"lineNumber" : 93,
|
||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
||||
"external" : true,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
}
|
||||
},
|
||||
"name" : "click.kamil.enterprise.machines.document.DocumentStateMachineConfiguration",
|
||||
"renderChoicesAsDiamonds" : true,
|
||||
"startStates" : [ "DocumentState.DRAFT" ],
|
||||
"transitions" : [ {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "DocumentState.DRAFT",
|
||||
"fullIdentifier" : "DocumentState.DRAFT"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "DocumentState.IN_REVIEW",
|
||||
"fullIdentifier" : "DocumentState.IN_REVIEW"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "DocumentEvent.SUBMIT",
|
||||
"fullIdentifier" : "DocumentEvent.SUBMIT"
|
||||
},
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "DocumentState.IN_REVIEW",
|
||||
"fullIdentifier" : "DocumentState.IN_REVIEW"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "DocumentState.APPROVED",
|
||||
"fullIdentifier" : "DocumentState.APPROVED"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "DocumentEvent.APPROVE",
|
||||
"fullIdentifier" : "DocumentEvent.APPROVE"
|
||||
},
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "DocumentState.IN_REVIEW",
|
||||
"fullIdentifier" : "DocumentState.IN_REVIEW"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "DocumentState.DRAFT",
|
||||
"fullIdentifier" : "DocumentState.DRAFT"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "DocumentEvent.REJECT",
|
||||
"fullIdentifier" : "DocumentEvent.REJECT"
|
||||
},
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
"endStates" : [ "DocumentState.APPROVED" ]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,33 @@
|
||||
@startuml
|
||||
!pragma layout smetana
|
||||
set separator none
|
||||
hide empty description
|
||||
hide stereotype
|
||||
skinparam state {
|
||||
BackgroundColor white
|
||||
BorderColor #94a3b8
|
||||
BorderThickness 1
|
||||
FontName Inter
|
||||
FontSize 9
|
||||
FontStyle bold
|
||||
RoundCorner 20
|
||||
Padding 1
|
||||
}
|
||||
skinparam shadowing false
|
||||
skinparam ArrowFontName JetBrains Mono
|
||||
skinparam ArrowFontSize 8
|
||||
skinparam ArrowColor #cbd5e1
|
||||
skinparam ArrowThickness 1
|
||||
skinparam dpi 110
|
||||
skinparam svgLinkTarget _self
|
||||
|
||||
[*] --> DocumentState.DRAFT
|
||||
|
||||
|
||||
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.IN_REVIEW <<external>> : DocumentEvent.SUBMIT
|
||||
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentEvent.APPROVE
|
||||
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.DRAFT <<external>> : DocumentEvent.REJECT
|
||||
|
||||
DocumentState.APPROVED --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="DRAFT">
|
||||
<state id="DRAFT">
|
||||
<transition target="IN_REVIEW" event="DocumentEvent.SUBMIT"/>
|
||||
</state>
|
||||
<state id="IN_REVIEW">
|
||||
<transition target="APPROVED" event="DocumentEvent.APPROVE"/>
|
||||
<transition target="DRAFT" event="DocumentEvent.REJECT"/>
|
||||
</state>
|
||||
<state id="APPROVED">
|
||||
</state>
|
||||
</scxml>
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "PLACE_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
@@ -19,7 +22,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "CANCEL_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
@@ -29,7 +35,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "PAY_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
|
||||
@@ -39,7 +48,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "SHIP_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
||||
@@ -49,7 +61,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "RETURN_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
||||
@@ -59,7 +74,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
@@ -170,6 +188,78 @@
|
||||
} ]
|
||||
} ],
|
||||
"callChains" : [ {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/place",
|
||||
"className" : "click.kamil.examples.enterprise.api.OrderApi",
|
||||
"methodName" : "placeOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderApi.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/orders/place",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderApi.placeOrder", "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "PLACE_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
"methodName" : "place",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : [ "PLACE_ORDER" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "NEW",
|
||||
"targetState" : "CHECK_AVAILABILITY",
|
||||
"event" : "PLACE_ORDER"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/{id}/cancel",
|
||||
"className" : "click.kamil.examples.enterprise.api.OrderApi",
|
||||
"methodName" : "cancelOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/OrderApi.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/enterprise/orders/{id}/cancel",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "id",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderApi.cancelOrder", "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "CANCEL_ORDER",
|
||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||
"methodName" : "cancel",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : [ "CANCEL_ORDER" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "PAID",
|
||||
"targetState" : "CANCELLED",
|
||||
"event" : "CANCEL_ORDER"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/enterprise/orders/place",
|
||||
@@ -192,7 +282,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : [ "PLACE_ORDER" ]
|
||||
"polymorphicEvents" : [ "PLACE_ORDER" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -227,7 +320,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : [ "CANCEL_ORDER" ]
|
||||
"polymorphicEvents" : [ "CANCEL_ORDER" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -257,7 +353,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -288,7 +387,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : [ "PAY_ORDER" ]
|
||||
"polymorphicEvents" : [ "PAY_ORDER" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -323,7 +425,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -358,7 +463,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -388,7 +496,7 @@
|
||||
"rawName" : "OrderEvents.PLACE",
|
||||
"fullIdentifier" : "PLACE_ORDER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -402,14 +510,14 @@
|
||||
"fullIdentifier" : "PENDING_PAYMENT"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "(c) -> true",
|
||||
"isLambda" : true,
|
||||
"internalLogic" : "(c) -> true",
|
||||
"lineNumber" : 41,
|
||||
"className" : "click.kamil.examples.enterprise.config.EnterpriseStateMachineConfig",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/config/EnterpriseStateMachineConfig.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -423,7 +531,7 @@
|
||||
"fullIdentifier" : "CANCELLED"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -440,7 +548,7 @@
|
||||
"rawName" : "OrderEvents.PAY",
|
||||
"fullIdentifier" : "PAY_ORDER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -457,7 +565,7 @@
|
||||
"rawName" : "OrderEvents.SHIP",
|
||||
"fullIdentifier" : "SHIP_ORDER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -474,7 +582,7 @@
|
||||
"rawName" : "\"FINALIZE\"",
|
||||
"fullIdentifier" : "FINALIZE"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -491,7 +599,7 @@
|
||||
"rawName" : "OrderEvents.CANCEL",
|
||||
"fullIdentifier" : "CANCEL_ORDER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -508,7 +616,7 @@
|
||||
"rawName" : "OrderEvents.RETURN",
|
||||
"fullIdentifier" : "RETURN_ORDER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 34,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "AUDIT_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor",
|
||||
@@ -19,7 +22,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "FALLBACK_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||
@@ -29,7 +35,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "PROFILED_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||
@@ -39,7 +48,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "PRIMARY_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||
@@ -49,7 +61,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "SUBMIT_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
|
||||
@@ -59,7 +74,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 20,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "CANCEL_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
|
||||
@@ -69,7 +87,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "[LIFECYCLE:RESTORE]",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
|
||||
@@ -79,7 +100,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 29,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "QUALIFIER_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||
@@ -89,7 +113,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "NAMED_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||
@@ -99,7 +126,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "AUTHORIZE",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||
@@ -109,7 +139,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 22,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "CAPTURE",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||
@@ -119,7 +152,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 35,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "[LIFECYCLE:RESTORE]",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||
@@ -129,7 +165,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 28,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "REACTIVE_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
|
||||
@@ -139,7 +178,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
@@ -367,7 +409,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 20,
|
||||
"polymorphicEvents" : [ "SUBMIT_EVENT" ]
|
||||
"polymorphicEvents" : [ "SUBMIT_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -398,7 +443,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : [ "CANCEL_EVENT" ]
|
||||
"polymorphicEvents" : [ "CANCEL_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -429,7 +477,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 34,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -464,7 +515,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 29,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : "orderId",
|
||||
"matchedTransitions" : null
|
||||
@@ -495,7 +549,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : [ "REACTIVE_EVENT" ]
|
||||
"polymorphicEvents" : [ "REACTIVE_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -525,7 +582,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -556,7 +616,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ]
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -583,7 +646,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ]
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -610,7 +676,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ]
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -637,7 +706,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ]
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -664,7 +736,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ]
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -691,7 +766,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ]
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -718,7 +796,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ]
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -749,7 +830,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 22,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : [ "AUTHORIZE" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -780,7 +864,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 35,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : [ "CAPTURE" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : "paymentId",
|
||||
"matchedTransitions" : null
|
||||
@@ -811,7 +898,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 28,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : "paymentId",
|
||||
"matchedTransitions" : null
|
||||
@@ -842,7 +932,7 @@
|
||||
"rawName" : "MyEvents.SUBMIT",
|
||||
"fullIdentifier" : "SUBMIT_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -859,7 +949,7 @@
|
||||
"rawName" : "\"FINISH\"",
|
||||
"fullIdentifier" : "FINISH"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -876,7 +966,7 @@
|
||||
"rawName" : "MyEvents.CANCEL",
|
||||
"fullIdentifier" : "CANCEL_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -893,7 +983,7 @@
|
||||
"rawName" : "\"REACTIVE_EVENT\"",
|
||||
"fullIdentifier" : "REACTIVE_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -910,7 +1000,7 @@
|
||||
"rawName" : "\"AUDIT_EVENT\"",
|
||||
"fullIdentifier" : "AUDIT_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -927,7 +1017,7 @@
|
||||
"rawName" : "\"EXTERNAL_TRIGGER\"",
|
||||
"fullIdentifier" : "EXTERNAL_TRIGGER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 34,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "AUDIT_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor",
|
||||
@@ -19,7 +22,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "FALLBACK_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||
@@ -29,7 +35,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "PROFILED_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||
@@ -39,7 +48,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "PRIMARY_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||
@@ -49,7 +61,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "SUBMIT_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
|
||||
@@ -59,7 +74,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 20,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "CANCEL_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
|
||||
@@ -69,7 +87,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "[LIFECYCLE:RESTORE]",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.OrderService",
|
||||
@@ -79,7 +100,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 29,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "QUALIFIER_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||
@@ -89,7 +113,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "NAMED_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||
@@ -99,7 +126,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "AUTHORIZE",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||
@@ -109,7 +139,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 22,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "CAPTURE",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||
@@ -119,7 +152,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 35,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "[LIFECYCLE:RESTORE]",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||
@@ -129,7 +165,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 28,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "REACTIVE_EVENT",
|
||||
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
|
||||
@@ -139,7 +178,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
@@ -367,7 +409,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 20,
|
||||
"polymorphicEvents" : [ "SUBMIT_EVENT" ]
|
||||
"polymorphicEvents" : [ "SUBMIT_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -398,7 +443,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 25,
|
||||
"polymorphicEvents" : [ "CANCEL_EVENT" ]
|
||||
"polymorphicEvents" : [ "CANCEL_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -429,7 +477,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 34,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -464,7 +515,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 29,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : "orderId",
|
||||
"matchedTransitions" : null
|
||||
@@ -495,7 +549,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : [ "REACTIVE_EVENT" ]
|
||||
"polymorphicEvents" : [ "REACTIVE_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -525,7 +582,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 16,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -556,7 +616,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ]
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -583,7 +646,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ]
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -610,7 +676,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ]
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -637,7 +706,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ]
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -664,7 +736,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 19,
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ]
|
||||
"polymorphicEvents" : [ "PRIMARY_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -691,7 +766,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ]
|
||||
"polymorphicEvents" : [ "QUALIFIER_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -718,7 +796,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ]
|
||||
"polymorphicEvents" : [ "NAMED_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -749,7 +830,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 22,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : [ "AUTHORIZE" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
@@ -780,7 +864,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 35,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : [ "CAPTURE" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : "paymentId",
|
||||
"matchedTransitions" : null
|
||||
@@ -811,7 +898,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 28,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : "paymentId",
|
||||
"matchedTransitions" : null
|
||||
@@ -842,7 +932,7 @@
|
||||
"rawName" : "MyEvents.SUBMIT",
|
||||
"fullIdentifier" : "SUBMIT_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -859,7 +949,7 @@
|
||||
"rawName" : "\"FINISH\"",
|
||||
"fullIdentifier" : "FINISH"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -876,7 +966,7 @@
|
||||
"rawName" : "MyEvents.CANCEL",
|
||||
"fullIdentifier" : "CANCEL_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -893,7 +983,7 @@
|
||||
"rawName" : "\"REACTIVE_EVENT\"",
|
||||
"fullIdentifier" : "REACTIVE_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -910,7 +1000,7 @@
|
||||
"rawName" : "\"AUDIT_EVENT\"",
|
||||
"fullIdentifier" : "AUDIT_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -927,7 +1017,7 @@
|
||||
"rawName" : "\"EXTERNAL_TRIGGER\"",
|
||||
"fullIdentifier" : "EXTERNAL_TRIGGER"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.EVENT1",
|
||||
"fullIdentifier" : "Events.EVENT1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "Events.EVENT2",
|
||||
"fullIdentifier" : "Events.EVENT2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.EVENT3",
|
||||
"fullIdentifier" : "Events.EVENT3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.EVENT4",
|
||||
"fullIdentifier" : "Events.EVENT4"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"rawName" : "Events.EVENT5",
|
||||
"fullIdentifier" : "Events.EVENT5"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.EVENT6",
|
||||
"fullIdentifier" : "Events.EVENT6"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -128,7 +128,7 @@
|
||||
"rawName" : "Events.EVENT7",
|
||||
"fullIdentifier" : "Events.EVENT7"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -145,7 +145,7 @@
|
||||
"rawName" : "Events.EVENT8",
|
||||
"fullIdentifier" : "Events.EVENT8"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -162,7 +162,7 @@
|
||||
"rawName" : "Events.EVENT9",
|
||||
"fullIdentifier" : "Events.EVENT9"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -179,7 +179,7 @@
|
||||
"rawName" : "Events.EVENT10",
|
||||
"fullIdentifier" : "Events.EVENT10"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -196,7 +196,7 @@
|
||||
"rawName" : "Events.EVENT11",
|
||||
"fullIdentifier" : "Events.EVENT11"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -213,7 +213,7 @@
|
||||
"rawName" : "Events.EVENT12",
|
||||
"fullIdentifier" : "Events.EVENT12"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -230,7 +230,7 @@
|
||||
"rawName" : "Events.EVENT13",
|
||||
"fullIdentifier" : "Events.EVENT13"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -247,7 +247,7 @@
|
||||
"rawName" : "Events.EVENT14",
|
||||
"fullIdentifier" : "Events.EVENT14"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -264,7 +264,7 @@
|
||||
"rawName" : "Events.EVENT15",
|
||||
"fullIdentifier" : "Events.EVENT15"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -281,7 +281,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -298,7 +298,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -315,7 +315,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -332,7 +332,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -349,7 +349,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -366,7 +366,7 @@
|
||||
"rawName" : "Events.EVENTY",
|
||||
"fullIdentifier" : "Events.EVENTY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -380,14 +380,14 @@
|
||||
"fullIdentifier" : "States.STATEX"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 120,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -401,7 +401,7 @@
|
||||
"fullIdentifier" : "States.STATEZ"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -415,14 +415,14 @@
|
||||
"fullIdentifier" : "States.STATE17"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 126,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -436,14 +436,14 @@
|
||||
"fullIdentifier" : "States.STATE18"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value2\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 127,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -457,7 +457,7 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -471,14 +471,14 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header1\",\"foo\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 132,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -492,7 +492,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -506,14 +506,14 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value3\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 137,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -527,7 +527,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -541,14 +541,14 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"reset\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 142,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -562,7 +562,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -576,14 +576,14 @@
|
||||
"fullIdentifier" : "States.STATE5"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header2\",\"bar\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 147,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -597,7 +597,7 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -611,14 +611,14 @@
|
||||
"fullIdentifier" : "States.STATE13"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo13\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 152,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -632,14 +632,14 @@
|
||||
"fullIdentifier" : "States.STATE14"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo14\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 153,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -653,7 +653,7 @@
|
||||
"fullIdentifier" : "States.STATE15"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -667,14 +667,14 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goBack10\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 158,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -688,7 +688,7 @@
|
||||
"fullIdentifier" : "States.STATE11"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -702,14 +702,14 @@
|
||||
"fullIdentifier" : "States.STATE12"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"loop12\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 163,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -723,7 +723,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -737,14 +737,14 @@
|
||||
"fullIdentifier" : "States.STATE8"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBack\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 168,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -758,14 +758,14 @@
|
||||
"fullIdentifier" : "States.STATE7"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBackMore\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 169,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -779,7 +779,7 @@
|
||||
"fullIdentifier" : "States.STATE6"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -793,14 +793,14 @@
|
||||
"fullIdentifier" : "States.STATE9"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"forward9\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 174,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -814,7 +814,7 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -831,7 +831,7 @@
|
||||
"rawName" : "Events.EVENTX",
|
||||
"fullIdentifier" : "Events.EVENTX"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -845,14 +845,14 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header1\",\"foo\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 31,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.F1StateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/F1StateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -866,7 +866,7 @@
|
||||
"fullIdentifier" : "States.STATE_EXTRA_1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -883,7 +883,7 @@
|
||||
"rawName" : "Events.EVENTY",
|
||||
"fullIdentifier" : "Events.EVENTY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -900,7 +900,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL_2",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -917,7 +917,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL_2",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.EVENT1",
|
||||
"fullIdentifier" : "Events.EVENT1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "Events.EVENT2",
|
||||
"fullIdentifier" : "Events.EVENT2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.EVENT3",
|
||||
"fullIdentifier" : "Events.EVENT3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.EVENT4",
|
||||
"fullIdentifier" : "Events.EVENT4"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"rawName" : "Events.EVENT5",
|
||||
"fullIdentifier" : "Events.EVENT5"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.EVENT6",
|
||||
"fullIdentifier" : "Events.EVENT6"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -128,7 +128,7 @@
|
||||
"rawName" : "Events.EVENT7",
|
||||
"fullIdentifier" : "Events.EVENT7"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -145,7 +145,7 @@
|
||||
"rawName" : "Events.EVENT8",
|
||||
"fullIdentifier" : "Events.EVENT8"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -162,7 +162,7 @@
|
||||
"rawName" : "Events.EVENT9",
|
||||
"fullIdentifier" : "Events.EVENT9"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -179,7 +179,7 @@
|
||||
"rawName" : "Events.EVENT10",
|
||||
"fullIdentifier" : "Events.EVENT10"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -196,7 +196,7 @@
|
||||
"rawName" : "Events.EVENT11",
|
||||
"fullIdentifier" : "Events.EVENT11"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -213,7 +213,7 @@
|
||||
"rawName" : "Events.EVENT12",
|
||||
"fullIdentifier" : "Events.EVENT12"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -230,7 +230,7 @@
|
||||
"rawName" : "Events.EVENT13",
|
||||
"fullIdentifier" : "Events.EVENT13"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -247,7 +247,7 @@
|
||||
"rawName" : "Events.EVENT14",
|
||||
"fullIdentifier" : "Events.EVENT14"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -264,7 +264,7 @@
|
||||
"rawName" : "Events.EVENT15",
|
||||
"fullIdentifier" : "Events.EVENT15"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -281,7 +281,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -298,7 +298,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -315,7 +315,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -332,7 +332,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -349,7 +349,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -366,7 +366,7 @@
|
||||
"rawName" : "Events.EVENTY",
|
||||
"fullIdentifier" : "Events.EVENTY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -380,14 +380,14 @@
|
||||
"fullIdentifier" : "States.STATEX"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 120,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -401,7 +401,7 @@
|
||||
"fullIdentifier" : "States.STATEZ"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -415,14 +415,14 @@
|
||||
"fullIdentifier" : "States.STATE17"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 126,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -436,14 +436,14 @@
|
||||
"fullIdentifier" : "States.STATE18"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value2\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 127,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -457,7 +457,7 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -471,14 +471,14 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header1\",\"foo\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 132,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -492,7 +492,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -506,14 +506,14 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value3\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 137,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -527,7 +527,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -541,14 +541,14 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"reset\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 142,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -562,7 +562,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -576,14 +576,14 @@
|
||||
"fullIdentifier" : "States.STATE5"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header2\",\"bar\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 147,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -597,7 +597,7 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -611,14 +611,14 @@
|
||||
"fullIdentifier" : "States.STATE13"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo13\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 152,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -632,14 +632,14 @@
|
||||
"fullIdentifier" : "States.STATE14"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo14\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 153,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -653,7 +653,7 @@
|
||||
"fullIdentifier" : "States.STATE15"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -667,14 +667,14 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goBack10\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 158,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -688,7 +688,7 @@
|
||||
"fullIdentifier" : "States.STATE11"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -702,14 +702,14 @@
|
||||
"fullIdentifier" : "States.STATE12"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"loop12\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 163,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -723,7 +723,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -737,14 +737,14 @@
|
||||
"fullIdentifier" : "States.STATE8"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBack\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 168,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -758,14 +758,14 @@
|
||||
"fullIdentifier" : "States.STATE7"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBackMore\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 169,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -779,7 +779,7 @@
|
||||
"fullIdentifier" : "States.STATE6"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -793,14 +793,14 @@
|
||||
"fullIdentifier" : "States.STATE9"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"forward9\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 174,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractFStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractFStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -814,7 +814,7 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -831,7 +831,7 @@
|
||||
"rawName" : "Events.EVENT_1_1",
|
||||
"fullIdentifier" : "Events.EVENT_1_1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -845,14 +845,14 @@
|
||||
"fullIdentifier" : "States.STATE_EXTRA_1_1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header1\",\"foo\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 33,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.F2StateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/F2StateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -866,7 +866,7 @@
|
||||
"fullIdentifier" : "States.STATE_EXTRA_1_3"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -883,7 +883,7 @@
|
||||
"rawName" : "Events.EVENT_1_2",
|
||||
"fullIdentifier" : "Events.EVENT_1_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -900,7 +900,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL_2",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -917,7 +917,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL_2",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -934,7 +934,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL_2",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.TO_FORK",
|
||||
"fullIdentifier" : "Events.TO_FORK"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"fullIdentifier" : "States.REGION2_STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.R1_NEXT",
|
||||
"fullIdentifier" : "Events.R1_NEXT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.R2_NEXT",
|
||||
"fullIdentifier" : "Events.R2_NEXT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"fullIdentifier" : "States.JOIN"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.TO_END",
|
||||
"fullIdentifier" : "Events.TO_END"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.EVENT1",
|
||||
"fullIdentifier" : "Events.EVENT1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "Events.EVENT2",
|
||||
"fullIdentifier" : "Events.EVENT2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.EVENT3",
|
||||
"fullIdentifier" : "Events.EVENT3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.EVENT4",
|
||||
"fullIdentifier" : "Events.EVENT4"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"rawName" : "Events.EVENT5",
|
||||
"fullIdentifier" : "Events.EVENT5"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.EVENT6",
|
||||
"fullIdentifier" : "Events.EVENT6"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -128,7 +128,7 @@
|
||||
"rawName" : "Events.EVENT7",
|
||||
"fullIdentifier" : "Events.EVENT7"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -145,7 +145,7 @@
|
||||
"rawName" : "Events.EVENT8",
|
||||
"fullIdentifier" : "Events.EVENT8"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -162,7 +162,7 @@
|
||||
"rawName" : "Events.EVENT9",
|
||||
"fullIdentifier" : "Events.EVENT9"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -179,7 +179,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -196,7 +196,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -213,7 +213,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -230,7 +230,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -247,7 +247,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -264,7 +264,7 @@
|
||||
"rawName" : "Events.EVENTY",
|
||||
"fullIdentifier" : "Events.EVENTY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -278,14 +278,14 @@
|
||||
"fullIdentifier" : "States.STATEX"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 98,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -299,7 +299,7 @@
|
||||
"fullIdentifier" : "States.STATEZ"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -313,14 +313,14 @@
|
||||
"fullIdentifier" : "States.STATE8"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBack\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 104,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -334,14 +334,14 @@
|
||||
"fullIdentifier" : "States.STATE7"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBackMore\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 105,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -355,7 +355,7 @@
|
||||
"fullIdentifier" : "States.STATE6"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -369,14 +369,14 @@
|
||||
"fullIdentifier" : "States.STATE9"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"forward9\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 110,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -390,7 +390,7 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -407,7 +407,7 @@
|
||||
"rawName" : "Events.EVENT_1_2",
|
||||
"fullIdentifier" : "Events.EVENT_1_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -424,7 +424,7 @@
|
||||
"rawName" : "Events.EVENT_1_2",
|
||||
"fullIdentifier" : "Events.EVENT_1_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -441,7 +441,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.EVENT1",
|
||||
"fullIdentifier" : "Events.EVENT1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "Events.EVENT2",
|
||||
"fullIdentifier" : "Events.EVENT2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.EVENT3",
|
||||
"fullIdentifier" : "Events.EVENT3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.EVENT4",
|
||||
"fullIdentifier" : "Events.EVENT4"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"rawName" : "Events.EVENT5",
|
||||
"fullIdentifier" : "Events.EVENT5"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.EVENT6",
|
||||
"fullIdentifier" : "Events.EVENT6"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -128,7 +128,7 @@
|
||||
"rawName" : "Events.EVENT7",
|
||||
"fullIdentifier" : "Events.EVENT7"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -145,7 +145,7 @@
|
||||
"rawName" : "Events.EVENT8",
|
||||
"fullIdentifier" : "Events.EVENT8"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -162,7 +162,7 @@
|
||||
"rawName" : "Events.EVENT9",
|
||||
"fullIdentifier" : "Events.EVENT9"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -179,7 +179,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -196,7 +196,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -213,7 +213,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -230,7 +230,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -247,7 +247,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -264,7 +264,7 @@
|
||||
"rawName" : "Events.EVENTY",
|
||||
"fullIdentifier" : "Events.EVENTY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -278,14 +278,14 @@
|
||||
"fullIdentifier" : "States.STATEX"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 98,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -299,7 +299,7 @@
|
||||
"fullIdentifier" : "States.STATEZ"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -313,14 +313,14 @@
|
||||
"fullIdentifier" : "States.STATE8"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBack\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 104,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -334,14 +334,14 @@
|
||||
"fullIdentifier" : "States.STATE7"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBackMore\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 105,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -355,7 +355,7 @@
|
||||
"fullIdentifier" : "States.STATE6"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -369,14 +369,14 @@
|
||||
"fullIdentifier" : "States.STATE9"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"forward9\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 110,
|
||||
"className" : "click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.AbstractGStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate3/AbstractGStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -390,7 +390,7 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -407,7 +407,7 @@
|
||||
"rawName" : "Events.EVENT_1_2",
|
||||
"fullIdentifier" : "Events.EVENT_1_2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -424,7 +424,7 @@
|
||||
"rawName" : "Events.EVENT_1_3",
|
||||
"fullIdentifier" : "Events.EVENT_1_3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -441,7 +441,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -458,7 +458,7 @@
|
||||
"rawName" : "Events.EVENT_CANCEL",
|
||||
"fullIdentifier" : "Events.EVENT_CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 15,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
@@ -35,6 +38,40 @@
|
||||
"parameters" : [ ]
|
||||
} ],
|
||||
"callChains" : [ {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/v2/orders/submit",
|
||||
"className" : "click.kamil.examples.statemachine.inheritance.api.OrderApi",
|
||||
"methodName" : "submitOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/api/OrderApi.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/v2/orders/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.inheritance.api.OrderApi.submitOrder", "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl.submitOrder", "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl.doProcess" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "INHERITED_SUBMIT",
|
||||
"className" : "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl",
|
||||
"methodName" : "doProcess",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 15,
|
||||
"polymorphicEvents" : [ "INHERITED_SUBMIT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "START",
|
||||
"targetState" : "WORKING",
|
||||
"event" : "INHERITED_SUBMIT"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/v2/orders/submit",
|
||||
@@ -57,7 +94,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 15,
|
||||
"polymorphicEvents" : [ "INHERITED_SUBMIT" ]
|
||||
"polymorphicEvents" : [ "INHERITED_SUBMIT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -87,7 +127,7 @@
|
||||
"rawName" : "\"INHERITED_SUBMIT\"",
|
||||
"fullIdentifier" : "INHERITED_SUBMIT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "OrderEvents.PAY",
|
||||
"fullIdentifier" : "OrderEvents.PAY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "OrderEvents.FULFILL",
|
||||
"fullIdentifier" : "OrderEvents.FULFILL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,14 +60,14 @@
|
||||
"rawName" : "OrderEvents.CANCEL",
|
||||
"fullIdentifier" : "OrderEvents.CANCEL"
|
||||
},
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n",
|
||||
"isLambda" : true,
|
||||
"internalLogic" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n",
|
||||
"lineNumber" : 29,
|
||||
"className" : "click.kamil.examples.statemachine.enumstate.KamilEnumStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/enumstate/KamilEnumStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -84,7 +84,7 @@
|
||||
"rawName" : "OrderEvents.CANCEL",
|
||||
"fullIdentifier" : "OrderEvents.CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -98,7 +98,7 @@
|
||||
"rawName" : "OrderEvents.ABCD",
|
||||
"fullIdentifier" : "OrderEvents.ABCD"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -115,7 +115,7 @@
|
||||
"rawName" : "OrderEvents.ABCD",
|
||||
"fullIdentifier" : "OrderEvents.ABCD"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -129,14 +129,14 @@
|
||||
"fullIdentifier" : "OrderStates.PAID2"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return true;\n }\n}\n",
|
||||
"isLambda" : true,
|
||||
"internalLogic" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return true;\n }\n}\n",
|
||||
"lineNumber" : 46,
|
||||
"className" : "click.kamil.examples.statemachine.enumstate.KamilEnumStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/enumstate/KamilEnumStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -150,14 +150,14 @@
|
||||
"fullIdentifier" : "OrderStates.PAID3"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guard1",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : null,
|
||||
"lineNumber" : 52,
|
||||
"className" : "click.kamil.examples.statemachine.enumstate.KamilEnumStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/enumstate/KamilEnumStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -171,14 +171,14 @@
|
||||
"fullIdentifier" : "OrderStates.HAPPEN"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n",
|
||||
"isLambda" : true,
|
||||
"internalLogic" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n",
|
||||
"lineNumber" : 53,
|
||||
"className" : "click.kamil.examples.statemachine.enumstate.KamilEnumStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/enumstate/KamilEnumStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -192,7 +192,7 @@
|
||||
"fullIdentifier" : "OrderStates.CANCELED"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 3
|
||||
}, {
|
||||
@@ -206,7 +206,7 @@
|
||||
"fullIdentifier" : "OrderStates.CANCELED"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -220,7 +220,7 @@
|
||||
"fullIdentifier" : "OrderStates.CANCELED"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -234,7 +234,7 @@
|
||||
"rawName" : "OrderEvents.ABCD",
|
||||
"fullIdentifier" : "OrderEvents.ABCD"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ {
|
||||
"expression" : "new Action<OrderStates,OrderEvents>(){\n @Override public void execute( StateContext<OrderStates,OrderEvents> context){\n }\n}\n",
|
||||
"isLambda" : true,
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 40,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "ORDER_EVENT",
|
||||
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
|
||||
@@ -19,7 +22,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 52,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
@@ -106,7 +112,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 40,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -114,6 +123,82 @@
|
||||
"targetState" : "BUSY",
|
||||
"event" : "SUBMIT"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /maven/orders/submit",
|
||||
"className" : "click.kamil.maven.api.MavenOrderApi",
|
||||
"methodName" : "submitOrder",
|
||||
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/MavenOrderApi.java",
|
||||
"metadata" : {
|
||||
"path" : "/maven/orders/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestBody" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.maven.api.MavenOrderApi.submitOrder", "click.kamil.maven.core.MavenOrderStateMachine.OrderController.submitOrder" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "SUBMIT",
|
||||
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
|
||||
"methodName" : "submitOrder",
|
||||
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 40,
|
||||
"polymorphicEvents" : [ "SUBMIT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "INIT",
|
||||
"targetState" : "BUSY",
|
||||
"event" : "SUBMIT"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "JMS",
|
||||
"name" : "JMS: order.queue",
|
||||
"className" : "click.kamil.maven.api.JmsOrderListener",
|
||||
"methodName" : "onMessage",
|
||||
"sourceFile" : "api-module/src/main/java/click/kamil/maven/api/JmsOrderListener.java",
|
||||
"metadata" : {
|
||||
"protocol" : "JMS",
|
||||
"destination" : "order.queue"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "message",
|
||||
"type" : "String",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.maven.api.JmsOrderListener.onMessage", "click.kamil.maven.core.MavenOrderStateMachine.OrderService.onMessage" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "ORDER_EVENT",
|
||||
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
|
||||
"methodName" : "onMessage",
|
||||
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 52,
|
||||
"polymorphicEvents" : [ "ORDER_EVENT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "BUSY",
|
||||
"targetState" : "DONE",
|
||||
"event" : "ORDER_EVENT"
|
||||
} ]
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
@@ -136,7 +221,7 @@
|
||||
"rawName" : "\"SUBMIT\"",
|
||||
"fullIdentifier" : "SUBMIT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ {
|
||||
"expression" : "loggingAction()",
|
||||
"isLambda" : false,
|
||||
@@ -160,7 +245,7 @@
|
||||
"rawName" : "\"ORDER_EVENT\"",
|
||||
"fullIdentifier" : "ORDER_EVENT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"rawName" : "Events.EVENT1",
|
||||
"fullIdentifier" : "Events.EVENT1"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -43,7 +43,7 @@
|
||||
"rawName" : "Events.EVENT2",
|
||||
"fullIdentifier" : "Events.EVENT2"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -60,7 +60,7 @@
|
||||
"rawName" : "Events.EVENT3",
|
||||
"fullIdentifier" : "Events.EVENT3"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -77,7 +77,7 @@
|
||||
"rawName" : "Events.EVENT4",
|
||||
"fullIdentifier" : "Events.EVENT4"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -94,7 +94,7 @@
|
||||
"rawName" : "Events.EVENT5",
|
||||
"fullIdentifier" : "Events.EVENT5"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@
|
||||
"rawName" : "Events.EVENT6",
|
||||
"fullIdentifier" : "Events.EVENT6"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -128,7 +128,7 @@
|
||||
"rawName" : "Events.EVENT7",
|
||||
"fullIdentifier" : "Events.EVENT7"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -145,7 +145,7 @@
|
||||
"rawName" : "Events.EVENT8",
|
||||
"fullIdentifier" : "Events.EVENT8"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -162,7 +162,7 @@
|
||||
"rawName" : "Events.EVENT9",
|
||||
"fullIdentifier" : "Events.EVENT9"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -179,7 +179,7 @@
|
||||
"rawName" : "Events.EVENT10",
|
||||
"fullIdentifier" : "Events.EVENT10"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -196,7 +196,7 @@
|
||||
"rawName" : "Events.EVENT11",
|
||||
"fullIdentifier" : "Events.EVENT11"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -213,7 +213,7 @@
|
||||
"rawName" : "Events.EVENT12",
|
||||
"fullIdentifier" : "Events.EVENT12"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -230,7 +230,7 @@
|
||||
"rawName" : "Events.EVENT13",
|
||||
"fullIdentifier" : "Events.EVENT13"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -247,7 +247,7 @@
|
||||
"rawName" : "Events.EVENT14",
|
||||
"fullIdentifier" : "Events.EVENT14"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -264,7 +264,7 @@
|
||||
"rawName" : "Events.EVENT15",
|
||||
"fullIdentifier" : "Events.EVENT15"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -281,7 +281,7 @@
|
||||
"rawName" : "Events.EVENTY",
|
||||
"fullIdentifier" : "Events.EVENTY"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
@@ -295,14 +295,14 @@
|
||||
"fullIdentifier" : "States.STATEX"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 91,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -316,7 +316,7 @@
|
||||
"fullIdentifier" : "States.STATEZ"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -330,14 +330,14 @@
|
||||
"fullIdentifier" : "States.STATE17"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value1\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 97,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -351,14 +351,14 @@
|
||||
"fullIdentifier" : "States.STATE18"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value2\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 98,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -372,7 +372,7 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -386,14 +386,14 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header1\",\"foo\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 103,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -407,7 +407,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -421,14 +421,14 @@
|
||||
"fullIdentifier" : "States.STATE19"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"value3\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 108,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -442,7 +442,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -456,14 +456,14 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"reset\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 113,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -477,7 +477,7 @@
|
||||
"fullIdentifier" : "States.STATE20"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -491,14 +491,14 @@
|
||||
"fullIdentifier" : "States.STATE5"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardEventHeaderEquals(\"header2\",\"bar\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object header=context.getMessageHeader(headerName);\n return expected.equals(header);\n}\n",
|
||||
"lineNumber" : 118,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -512,7 +512,7 @@
|
||||
"fullIdentifier" : "States.STATE1"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -526,14 +526,14 @@
|
||||
"fullIdentifier" : "States.STATE13"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo13\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 123,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -547,14 +547,14 @@
|
||||
"fullIdentifier" : "States.STATE14"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goTo14\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 124,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -568,7 +568,7 @@
|
||||
"fullIdentifier" : "States.STATE15"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -582,14 +582,14 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"goBack10\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 129,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -603,7 +603,7 @@
|
||||
"fullIdentifier" : "States.STATE11"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -617,14 +617,14 @@
|
||||
"fullIdentifier" : "States.STATE12"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"loop12\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 134,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -638,7 +638,7 @@
|
||||
"fullIdentifier" : "States.STATE16"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -652,14 +652,14 @@
|
||||
"fullIdentifier" : "States.STATE8"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBack\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 139,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -673,14 +673,14 @@
|
||||
"fullIdentifier" : "States.STATE7"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"stepBackMore\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 140,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -694,7 +694,7 @@
|
||||
"fullIdentifier" : "States.STATE6"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 2
|
||||
}, {
|
||||
@@ -708,14 +708,14 @@
|
||||
"fullIdentifier" : "States.STATE9"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : {
|
||||
"guards" : [ {
|
||||
"expression" : "guardVarEquals(\"forward9\")",
|
||||
"isLambda" : false,
|
||||
"internalLogic" : "context -> {\n Object var=context.getExtendedState().getVariables().get(\"var\");\n return expected.equals(var);\n}\n",
|
||||
"lineNumber" : 145,
|
||||
"className" : "click.kamil.examples.statemachine.inheritancestate.AbstractOneStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritancestate/AbstractOneStateMachineConfiguration.java"
|
||||
},
|
||||
} ],
|
||||
"actions" : [ ],
|
||||
"order" : 0
|
||||
}, {
|
||||
@@ -729,7 +729,7 @@
|
||||
"fullIdentifier" : "States.STATE10"
|
||||
} ],
|
||||
"event" : null,
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : 1
|
||||
}, {
|
||||
@@ -746,7 +746,7 @@
|
||||
"rawName" : "Events.EVENTX",
|
||||
"fullIdentifier" : "Events.EVENTX"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
@@ -69,7 +72,48 @@
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : null
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "NEW",
|
||||
"targetState" : "PROCESSING",
|
||||
"event" : "SUBMIT"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /orders/submit",
|
||||
"className" : "click.kamil.multi.api.OrderApi",
|
||||
"methodName" : "submitOrder",
|
||||
"sourceFile" : "api-module/src/main/java/click/kamil/multi/api/OrderApi.java",
|
||||
"metadata" : {
|
||||
"path" : "/orders/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "orderId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestBody" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.multi.api.OrderApi.submitOrder", "click.kamil.multi.core.OrderController.submitOrder" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "SUBMIT",
|
||||
"className" : "click.kamil.multi.core.OrderController",
|
||||
"methodName" : "submitOrder",
|
||||
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 18,
|
||||
"polymorphicEvents" : [ "SUBMIT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
@@ -99,7 +143,7 @@
|
||||
"rawName" : "\"SUBMIT\"",
|
||||
"fullIdentifier" : "SUBMIT"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ {
|
||||
"expression" : "processAction()",
|
||||
"isLambda" : false,
|
||||
@@ -123,7 +167,7 @@
|
||||
"rawName" : "\"FINISH\"",
|
||||
"fullIdentifier" : "FINISH"
|
||||
},
|
||||
"guard" : null,
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
digraph statemachine {
|
||||
rankdir=LR;
|
||||
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
|
||||
edge [fontname="Arial", fontsize=10];
|
||||
|
||||
_start [shape=circle, label="", fillcolor=black, width=0.1];
|
||||
_start -> NEW;
|
||||
SHIPPED [fillcolor=lightgray];
|
||||
NEW -> PENDING [label="OrderEvent.PAY / λ", style="solid", color="black"];
|
||||
PENDING -> SHIPPED [label="OrderEvent.SHIP", style="solid", color="black"];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,686 @@
|
||||
{
|
||||
"metadata" : {
|
||||
"triggers" : [ {
|
||||
"event" : "OrderEvent.PAY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 30,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "OrderEvent.SHIP",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 37,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "DocumentEvent.SUBMIT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 44,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "DocumentEvent.APPROVE",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 51,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "DocumentEvent.REJECT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 58,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "UserEvent.VERIFY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 65,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "UserEvent.SUSPEND",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 72,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "ORDER",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "DOCUMENT",
|
||||
"lineNumber" : 87,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "USER",
|
||||
"lineNumber" : 93,
|
||||
"polymorphicEvents" : null,
|
||||
"external" : false,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/pay",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/ship",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/ship",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/submit",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/approve",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/approve",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/reject",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/reject",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/verify",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/verify",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/suspend",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/suspend",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
} ],
|
||||
"callChains" : [ {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/pay",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.payOrder", "click.kamil.enterprise.web.StateMachineDispatcher.payOrder" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "OrderEvent.PAY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "payOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 30,
|
||||
"polymorphicEvents" : [ "OrderEvent.PAY" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderState.NEW",
|
||||
"targetState" : "OrderState.PENDING",
|
||||
"event" : "OrderEvent.PAY"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/order/ship",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/order/ship",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.shipOrder", "click.kamil.enterprise.web.StateMachineDispatcher.shipOrder" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "OrderEvent.SHIP",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "shipOrder",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 37,
|
||||
"polymorphicEvents" : [ "OrderEvent.SHIP" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderState.PENDING",
|
||||
"targetState" : "OrderState.SHIPPED",
|
||||
"event" : "OrderEvent.SHIP"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/submit",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/submit",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.submitDocument", "click.kamil.enterprise.web.StateMachineDispatcher.submitDocument" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "DocumentEvent.SUBMIT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "submitDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 44,
|
||||
"polymorphicEvents" : [ "DocumentEvent.SUBMIT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/approve",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/approve",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.approveDocument", "click.kamil.enterprise.web.StateMachineDispatcher.approveDocument" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "DocumentEvent.APPROVE",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "approveDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 51,
|
||||
"polymorphicEvents" : [ "DocumentEvent.APPROVE" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/document/reject",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/document/reject",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.rejectDocument", "click.kamil.enterprise.web.StateMachineDispatcher.rejectDocument" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "DocumentEvent.REJECT",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "rejectDocument",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 58,
|
||||
"polymorphicEvents" : [ "DocumentEvent.REJECT" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/verify",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/verify",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.verifyUser", "click.kamil.enterprise.web.StateMachineDispatcher.verifyUser" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "UserEvent.VERIFY",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "verifyUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 65,
|
||||
"polymorphicEvents" : [ "UserEvent.VERIFY" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/user/suspend",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/user/suspend",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.suspendUser", "click.kamil.enterprise.web.StateMachineDispatcher.suspendUser" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "UserEvent.SUSPEND",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "suspendUser",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 72,
|
||||
"polymorphicEvents" : [ "UserEvent.SUSPEND" ],
|
||||
"external" : false,
|
||||
"constraint" : null,
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "ORDER",
|
||||
"lineNumber" : 81,
|
||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
||||
"external" : true,
|
||||
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "DOCUMENT",
|
||||
"lineNumber" : 87,
|
||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
||||
"external" : true,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /api/machine/{machineType}/transition/{event}",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineController",
|
||||
"methodName" : "transition",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineController.java",
|
||||
"metadata" : {
|
||||
"path" : "/api/machine/{machineType}/transition/{event}",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "machineType",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "event",
|
||||
"type" : "String",
|
||||
"annotations" : [ "PathVariable" ]
|
||||
}, {
|
||||
"name" : "userId",
|
||||
"type" : "String",
|
||||
"annotations" : [ "RequestParam" ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.enterprise.web.StateMachineController.transition", "click.kamil.enterprise.web.StateMachineDispatcher.dispatch" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "true ? OrderEvent.valueOf(eventString.toUpperCase()) : true ? DocumentEvent.valueOf(eventString.toUpperCase()) : UserEvent.valueOf(eventString.toUpperCase())",
|
||||
"className" : "click.kamil.enterprise.web.StateMachineDispatcher",
|
||||
"methodName" : "dispatch",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/web/StateMachineDispatcher.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : "USER",
|
||||
"lineNumber" : 93,
|
||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
||||
"external" : true,
|
||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
||||
"ambiguous" : false
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : { }
|
||||
}
|
||||
},
|
||||
"name" : "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration",
|
||||
"renderChoicesAsDiamonds" : true,
|
||||
"startStates" : [ "OrderState.NEW" ],
|
||||
"transitions" : [ {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "OrderState.NEW",
|
||||
"fullIdentifier" : "OrderState.NEW"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "OrderState.PENDING",
|
||||
"fullIdentifier" : "OrderState.PENDING"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "OrderEvent.PAY",
|
||||
"fullIdentifier" : "OrderEvent.PAY"
|
||||
},
|
||||
"guards" : [ ],
|
||||
"actions" : [ {
|
||||
"expression" : "context -> System.out.println(\"Payment processed.\")",
|
||||
"isLambda" : true,
|
||||
"internalLogic" : "context -> System.out.println(\"Payment processed.\")",
|
||||
"lineNumber" : 28,
|
||||
"className" : "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration",
|
||||
"sourceFile" : "src/main/java/click/kamil/enterprise/machines/order/OrderStateMachineConfiguration.java"
|
||||
} ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "OrderState.PENDING",
|
||||
"fullIdentifier" : "OrderState.PENDING"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "OrderState.SHIPPED",
|
||||
"fullIdentifier" : "OrderState.SHIPPED"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "OrderEvent.SHIP",
|
||||
"fullIdentifier" : "OrderEvent.SHIP"
|
||||
},
|
||||
"guards" : [ ],
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
"endStates" : [ "OrderState.SHIPPED" ]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,32 @@
|
||||
@startuml
|
||||
!pragma layout smetana
|
||||
set separator none
|
||||
hide empty description
|
||||
hide stereotype
|
||||
skinparam state {
|
||||
BackgroundColor white
|
||||
BorderColor #94a3b8
|
||||
BorderThickness 1
|
||||
FontName Inter
|
||||
FontSize 9
|
||||
FontStyle bold
|
||||
RoundCorner 20
|
||||
Padding 1
|
||||
}
|
||||
skinparam shadowing false
|
||||
skinparam ArrowFontName JetBrains Mono
|
||||
skinparam ArrowFontSize 8
|
||||
skinparam ArrowColor #cbd5e1
|
||||
skinparam ArrowThickness 1
|
||||
skinparam dpi 110
|
||||
skinparam svgLinkTarget _self
|
||||
|
||||
[*] --> OrderState.NEW
|
||||
|
||||
|
||||
OrderState.NEW -[#1E90FF,bold]-> OrderState.PENDING <<external>> : OrderEvent.PAY / λ
|
||||
OrderState.PENDING -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderEvent.SHIP
|
||||
|
||||
OrderState.SHIPPED --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="NEW">
|
||||
<state id="NEW">
|
||||
<transition target="PENDING" event="OrderEvent.PAY"/>
|
||||
</state>
|
||||
<state id="PENDING">
|
||||
<transition target="SHIPPED" event="OrderEvent.SHIP"/>
|
||||
</state>
|
||||
<state id="SHIPPED">
|
||||
</state>
|
||||
</scxml>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user