321 lines
No EOL
11 KiB
Java
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;
|
|
}
|
|
} |