office-dynamixs-ai/dynamixs-ai-app/src/test/java/ai/dynamixs/model/builder/ProcessDescriptionParser.java
2026-01-16 21:57:24 +01:00

321 lines
No EOL
11 KiB
Java

package ai.dynamixs.model.builder;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openbpmn.bpmn.elements.Activity;
import org.openbpmn.bpmn.elements.BPMNProcess;
import org.openbpmn.bpmn.elements.Event;
import org.openbpmn.bpmn.elements.core.BPMNPoint;
import org.openbpmn.bpmn.exceptions.BPMNModelException;
/**
* Parses a process description file and generates BPMN elements.
* Uses LayoutCalculator for positioning of elements.
*
* Supports both plain text and Markdown-formatted descriptions.
*/
public class ProcessDescriptionParser {
private static final Logger logger = Logger.getLogger(ProcessDescriptionParser.class.getName());
// Regex patterns (flexible for plain text and Markdown)
private static final Pattern TASK_PATTERN = Pattern.compile(
"\\|-\\s*\\[Task:\\s*(\\d+)\\]\\s*(.+)$");
private static final Pattern EVENT_PATTERN = Pattern.compile(
"\\|--\\s*\\[Event:\\s*(\\d+)\\]\\s*(.+?)\\s*-->\\s*\\[Task:\\s*(\\d+)\\]");
private static final Pattern TEAM_PATTERN = Pattern.compile(
"Team:\\s*(.+)$");
// Layout calculator
private LayoutCalculator layoutCalculator = new LayoutCalculator();
// Storage for created elements
private Map<Integer, Activity> tasks = new HashMap<>();
private List<EventData> events = new ArrayList<>();
private Map<Integer, String> taskLanes = new HashMap<>();
private int sequenceFlowCounter = 1;
/**
* Internal class to hold event data before creating BPMN elements.
*/
private static class EventData {
int eventId;
String name;
int sourceTaskId;
int targetTaskId;
EventData(int eventId, String name, int sourceTaskId, int targetTaskId) {
this.eventId = eventId;
this.name = name;
this.sourceTaskId = sourceTaskId;
this.targetTaskId = targetTaskId;
}
String getKey() {
return sourceTaskId + "_" + eventId;
}
}
/**
* Simple data holder for tasks during parsing.
*/
private static class TaskData {
int id;
String name;
String description;
String team;
TaskData(int id, String name) {
this.id = id;
this.name = name;
}
}
/**
* Removes Markdown formatting (**, *, etc.) from a string.
*/
private String cleanMarkdown(String text) {
if (text == null)
return null;
// Remove ** and * markers
String cleaned = text.replaceAll("\\*\\*", "").replaceAll("\\*", "");
// Remove trailing comments like (Iteration für Korrekturen)
cleaned = cleaned.replaceAll("\\s*\\([^)]*\\)\\s*$", "");
return cleaned.trim();
}
/**
* Parses the description file and creates all BPMN elements in the process.
*/
public void parseAndGenerate(String filePath, BPMNProcess process,
Activity referenceTask, Event referenceEvent) throws IOException, BPMNModelException {
tasks.clear();
events.clear();
taskLanes.clear();
// Parse file and collect task/event data
List<TaskData> taskDataList = parseFile(filePath);
// Collect unique lanes (in order of first appearance)
List<String> laneNames = new ArrayList<>();
for (TaskData taskData : taskDataList) {
if (taskData.team != null && !laneNames.contains(taskData.team)) {
laneNames.add(taskData.team);
}
taskLanes.put(taskData.id, taskData.team);
}
// Create BPMN lanes
for (String laneName : laneNames) {
process.addLane(laneName);
logger.info("Created lane: " + laneName);
}
// Calculate layout
layoutCalculator.calculateLanePositions(laneNames);
List<Integer> taskIds = taskDataList.stream().map(t -> t.id).toList();
layoutCalculator.calculateTaskPositions(taskIds, taskLanes);
// Calculate layout for all events
for (EventData eventData : events) {
int eventIndex = getEventIndex(eventData.sourceTaskId, eventData.eventId);
String laneName = taskLanes.get(eventData.sourceTaskId);
layoutCalculator.calculateEventPosition(eventData.sourceTaskId, eventData.eventId, eventIndex, laneName);
}
// Create BPMN tasks
createTasks(taskDataList, process, referenceTask);
// Create BPMN events
Map<String, Event> createdEvents = createEvents(process, referenceEvent);
// Create sequence flows
createSequenceFlows(process, createdEvents);
logger.info("Generated " + tasks.size() + " tasks, " + createdEvents.size() + " events, " + laneNames.size()
+ " lanes");
}
/**
* Parses the file and returns task data list. Also populates events list.
*/
private List<TaskData> parseFile(String filePath) throws IOException {
List<TaskData> taskDataList = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
TaskData currentTask = null;
StringBuilder descBuilder = new StringBuilder();
boolean readingDesc = false;
while ((line = reader.readLine()) != null) {
String trimmed = line.trim();
// Match task
Matcher taskMatcher = TASK_PATTERN.matcher(trimmed);
if (taskMatcher.find()) {
if (currentTask != null) {
currentTask.description = descBuilder.toString().trim();
}
currentTask = new TaskData(
Integer.parseInt(taskMatcher.group(1)),
cleanMarkdown(taskMatcher.group(2)));
taskDataList.add(currentTask);
descBuilder = new StringBuilder();
readingDesc = false;
continue;
}
// Match team
Matcher teamMatcher = TEAM_PATTERN.matcher(trimmed);
if (teamMatcher.find() && currentTask != null) {
currentTask.team = cleanMarkdown(teamMatcher.group(1));
continue;
}
// Match event
Matcher eventMatcher = EVENT_PATTERN.matcher(trimmed);
if (eventMatcher.find() && currentTask != null) {
readingDesc = false;
events.add(new EventData(
Integer.parseInt(eventMatcher.group(1)),
cleanMarkdown(eventMatcher.group(2)),
currentTask.id,
Integer.parseInt(eventMatcher.group(3))));
continue;
}
// Description handling
if (trimmed.contains("Description:")) {
readingDesc = true;
continue;
}
if (readingDesc && currentTask != null && !trimmed.startsWith("|--")) {
String descLine = trimmed.replaceAll("^\\|\\s*", "").trim();
if (!descLine.isEmpty()) {
if (descBuilder.length() > 0)
descBuilder.append(" ");
descBuilder.append(descLine);
}
}
}
if (currentTask != null) {
currentTask.description = descBuilder.toString().trim();
}
}
return taskDataList;
}
/**
* Creates BPMN task elements.
*/
private void createTasks(List<TaskData> taskDataList, BPMNProcess process,
Activity referenceTask) throws BPMNModelException {
for (TaskData taskData : taskDataList) {
double[] pos = layoutCalculator.getTaskPosition(taskData.id);
Activity task = (Activity) process.cloneBPMNElementNode(referenceTask);
task.setName(taskData.name);
task.setPosition(new BPMNPoint(pos[0], pos[1]));
task.setAttribute("imixs:processid", String.valueOf(taskData.id));
if (taskData.description != null && !taskData.description.isEmpty()) {
task.setDocumentation(taskData.description);
}
tasks.put(taskData.id, task);
logger.info("Created task: " + taskData.id + " - " + taskData.name);
}
}
/**
* Creates BPMN event elements.
*/
private Map<String, Event> createEvents(BPMNProcess process,
Event referenceEvent) throws BPMNModelException {
Map<String, Event> createdEvents = new HashMap<>();
for (EventData eventData : events) {
double[] pos = layoutCalculator.getEventPosition(eventData.sourceTaskId, eventData.eventId);
if (pos == null)
continue;
Event event = (Event) process.cloneBPMNElementNode(referenceEvent);
event.setName(eventData.name);
event.setPosition(new BPMNPoint(pos[0], pos[1]));
// Calculate activity ID: first event = 10, second = 20, etc. (per task)
int eventIndex = getEventIndex(eventData.sourceTaskId, eventData.eventId);
int activityId = (eventIndex + 1) * 10;
event.setAttribute("imixs:activityid", String.valueOf(activityId));
createdEvents.put(eventData.getKey(), event);
logger.info("Created event: " + eventData.getKey() + " - " + eventData.name);
}
return createdEvents;
}
/**
* Creates sequence flows between tasks and events.
*/
private void createSequenceFlows(BPMNProcess process,
Map<String, Event> createdEvents) throws BPMNModelException {
for (EventData eventData : events) {
Activity sourceTask = tasks.get(eventData.sourceTaskId);
Event event = createdEvents.get(eventData.getKey());
Activity targetTask = tasks.get(eventData.targetTaskId);
if (sourceTask == null || event == null || targetTask == null)
continue;
if (eventData.sourceTaskId == eventData.targetTaskId) {
// Self-loop (CatchEvent): only sequence flow from event to task
process.addSequenceFlow("sf-" + sequenceFlowCounter++, event.getId(), targetTask.getId());
} else {
// Normal flow: task -> event -> target task
process.addSequenceFlow("sf-" + sequenceFlowCounter++, sourceTask.getId(), event.getId());
process.addSequenceFlow("sf-" + sequenceFlowCounter++, event.getId(), targetTask.getId());
}
}
}
/**
* Returns the index of an event among all events of a source task.
*/
private int getEventIndex(int taskId, int eventId) {
int index = 0;
for (EventData e : events) {
if (e.sourceTaskId == taskId) {
if (e.eventId == eventId)
return index;
index++;
}
}
return 0;
}
/**
* Returns the layout calculator for configuration.
*/
public LayoutCalculator getLayoutCalculator() {
return layoutCalculator;
}
}