diff --git a/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/ActivitiModelServiceImpl.java b/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/ActivitiModelServiceImpl.java index ac12b76f..4f0ef460 100644 --- a/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/ActivitiModelServiceImpl.java +++ b/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/ActivitiModelServiceImpl.java @@ -9,16 +9,17 @@ import ink.wgink.interfaces.consts.ISystemConstant; import ink.wgink.module.activiti.service.activiti.IActivitiModelService; import ink.wgink.util.ResourceUtil; import org.activiti.bpmn.model.BpmnModel; +import org.activiti.bpmn.model.FlowNode; +import org.activiti.bpmn.model.SequenceFlow; import org.activiti.editor.constants.ModelDataJsonConstants; import org.activiti.engine.HistoryService; -import org.activiti.engine.ProcessEngine; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; +import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.repository.Model; import org.activiti.engine.repository.ProcessDefinition; import org.activiti.engine.runtime.ProcessInstance; -import org.activiti.image.impl.DefaultProcessDiagramGenerator; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.PNGTranscoder; @@ -28,9 +29,7 @@ import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletResponse; import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; /** @@ -52,8 +51,6 @@ public class ActivitiModelServiceImpl extends DefaultBaseService implements IAct private RuntimeService runtimeService; @Autowired private HistoryService historyService; - @Autowired - private ProcessEngine processEngine; @Override public void saveModel(String modelId, String name, String description, String json_xml, String svg_xml) throws Exception { @@ -127,10 +124,11 @@ public class ActivitiModelServiceImpl extends DefaultBaseService implements IAct if (processDefinition == null) { throw new SearchException("流程定义不存在,请检查流程部署情况"); } + String fontName = "宋体"; BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); - DefaultProcessDiagramGenerator defaultProcessDiagramGenerator = new DefaultProcessDiagramGenerator(); + CustomProcessDiagramGenerator customProcessDiagramGenerator = new CustomProcessDiagramGenerator(); response.setContentType("image/png"); - try (InputStream inputStream = defaultProcessDiagramGenerator.generateDiagram(bpmnModel, "png", new ArrayList<>(), new ArrayList<>(), 1.0D); + try (InputStream inputStream = customProcessDiagramGenerator.generateDiagram(bpmnModel, "png", new ArrayList<>(), new ArrayList<>(), fontName, fontName, fontName, null, 1.0D); OutputStream outputStream = response.getOutputStream(); ) { byte[] buf = new byte[1024]; @@ -159,10 +157,16 @@ public class ActivitiModelServiceImpl extends DefaultBaseService implements IAct } } + /** + * 获取运行流程图输入流 + * + * @param processInstanceId + * @return + */ public InputStream getRuntimeProcessImageInputStream(String processInstanceId) { //获得流程实例 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); - String processDefinitionId = StringUtils.EMPTY; + String processDefinitionId; if (processInstance == null) { //查询已经结束的流程实例 HistoricProcessInstance processInstanceHistory = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); @@ -178,13 +182,79 @@ public class ActivitiModelServiceImpl extends DefaultBaseService implements IAct //使用宋体 String fontName = "宋体"; //获取BPMN模型对象 - BpmnModel model = repositoryService.getBpmnModel(processDefinitionId); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + //获取流程实例当前的节点,需要高亮显示 - List currentActs = Collections.EMPTY_LIST; + List highLightedActivitiIds = Collections.EMPTY_LIST; if (processInstance != null) { - currentActs = runtimeService.getActiveActivityIds(processInstance.getId()); + highLightedActivitiIds = runtimeService.getActiveActivityIds(processInstance.getId()); + // 获取历史节点 + List historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); + if (!historicActivityInstances.isEmpty()) { + for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) { + highLightedActivitiIds.add("#" + historicActivityInstance.getActivityId()); + } + } } - return processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator().generateDiagram(model, "png", currentActs, new ArrayList<>(), + List listSequenceFlows = listSequenceFlows(bpmnModel, highLightedActivitiIds); + CustomProcessDiagramGenerator customProcessDiagramGenerator = new CustomProcessDiagramGenerator(); + return customProcessDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitiIds, listSequenceFlows, fontName, fontName, fontName, null, 1D); } + + /** + * 序列流列表 + * + * @param bpmnModel 模型 + * @param highLightedActivitiIds 高亮的活动ID + * @return + */ + private List listSequenceFlows(BpmnModel bpmnModel, List highLightedActivitiIds) { + Set flowSet = new HashSet<>(); + // 获取有关的点 + List flowElementsOfType = bpmnModel.getProcesses().get(0).findFlowElementsOfType(FlowNode.class); + List activitiFlowNode = new ArrayList<>(); + // 加载高亮的节点 + for (FlowNode flowNode : flowElementsOfType) { + for (String highLightedActivitiId : highLightedActivitiIds) { + String activitiId = highLightedActivitiId; + if (activitiId.startsWith("#")) { + activitiId = highLightedActivitiId.substring(1); + } + if (StringUtils.equals(flowNode.getId(), activitiId)) { + activitiFlowNode.add(flowNode); + break; + } + } + } + // 找出高亮节点之间的连线 + for (int i = 0; i < activitiFlowNode.size() - 1; i++) { + FlowNode flowNode = activitiFlowNode.get(i); + List incomingFlows = flowNode.getIncomingFlows(); + List outgoingFlows = flowNode.getOutgoingFlows(); + for (int j = 0; j < activitiFlowNode.size(); j++) { + FlowNode nextFlowNode = activitiFlowNode.get(j); + List nextIncomingFlows = nextFlowNode.getIncomingFlows(); + List nextOutgoingFlows = nextFlowNode.getOutgoingFlows(); + // 前节点的出与后节点的入相同,则有关系 + for (SequenceFlow outgoingFlow : outgoingFlows) { + for (SequenceFlow nextIncomingFlow : nextIncomingFlows) { + if (StringUtils.equals(outgoingFlow.getId(), nextIncomingFlow.getId())) { + flowSet.add(outgoingFlow.getId()); + } + } + } + // 前节点的入与后节点的出相同,则有关系 + for (SequenceFlow incomingFlow : incomingFlows) { + for (SequenceFlow nextOutgoingFlow : nextOutgoingFlows) { + if (StringUtils.equals(incomingFlow.getId(), nextOutgoingFlow.getId())) { + flowSet.add(incomingFlow.getId()); + } + } + } + } + } + return new ArrayList<>(flowSet); + } + } diff --git a/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/CustomProcessDiagramCanvas.java b/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/CustomProcessDiagramCanvas.java new file mode 100644 index 00000000..dfb030c5 --- /dev/null +++ b/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/CustomProcessDiagramCanvas.java @@ -0,0 +1,1317 @@ +package ink.wgink.module.activiti.service.activiti.impl; + +import org.activiti.bpmn.model.AssociationDirection; +import org.activiti.bpmn.model.GraphicInfo; +import org.activiti.engine.ActivitiException; +import org.activiti.image.exception.ActivitiImageException; +import org.activiti.image.util.ReflectUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.font.LineBreakMeasurer; +import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; +import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; +import java.util.ArrayList; +import java.util.List; + +/** + * 定义流程图画布 + */ +public class CustomProcessDiagramCanvas { + + protected static final Logger LOGGER = LoggerFactory.getLogger(CustomProcessDiagramCanvas.class); + + public enum SHAPE_TYPE {Rectangle, Rhombus, Ellipse} + + // Predefined sized + protected static final int ARROW_WIDTH = 5; + protected static final int CONDITIONAL_INDICATOR_WIDTH = 16; + protected static final int DEFAULT_INDICATOR_WIDTH = 10; + protected static final int MARKER_WIDTH = 12; + protected static final int FONT_SIZE = 11; + protected static final int FONT_SPACING = 2; + protected static final int TEXT_PADDING = 3; + protected static final int ANNOTATION_TEXT_PADDING = 7; + protected static final int LINE_HEIGHT = FONT_SIZE + FONT_SPACING; + + + // Colors + protected static Color TASK_BOX_COLOR = new Color(249, 249, 249); + protected static Color SUBPROCESS_BOX_COLOR = new Color(255, 255, 255); + protected static Color EVENT_COLOR = new Color(255, 255, 255); + protected static Color CONNECTION_COLOR = new Color(88, 88, 88); + protected static Color CONDITIONAL_INDICATOR_COLOR = new Color(255, 255, 255); + protected static Color HIGHLIGHT_COLOR = new Color(255, 87, 34); + protected static Color HIGHLIGHT_HISTORY_COLOR = new Color(0, 150, 136); + protected static Color LABEL_COLOR = Color.BLACK; // new Color(112, 146, 190); + protected static Color TASK_BORDER_COLOR = new Color(187, 187, 187); + protected static Color EVENT_BORDER_COLOR = new Color(88, 88, 88); + protected static Color SUBPROCESS_BORDER_COLOR = new Color(0, 0, 0); + + // Fonts + protected static Font LABEL_FONT = null; + protected static Font ANNOTATION_FONT = null; + + // Strokes + protected static Stroke THICK_TASK_BORDER_STROKE = new BasicStroke(2.0f); + protected static Stroke GATEWAY_TYPE_STROKE = new BasicStroke(3.0f); + protected static Stroke END_EVENT_STROKE = new BasicStroke(3.0f); + protected static Stroke MULTI_INSTANCE_STROKE = new BasicStroke(1.3f); + protected static Stroke EVENT_SUBPROCESS_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{1.0f}, 0.0f); + protected static Stroke NON_INTERRUPTING_EVENT_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{4.0f, 3.0f}, 0.0f); + protected static Stroke HIGHLIGHT_FLOW_STROKE = new BasicStroke(1.3f); + protected static Stroke ANNOTATION_STROKE = new BasicStroke(2.0f); + protected static Stroke ASSOCIATION_STROKE = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, new float[]{2.0f, 2.0f}, 0.0f); + + // icons + protected static int ICON_PADDING = 5; + protected static BufferedImage USERTASK_IMAGE; + protected static BufferedImage SCRIPTTASK_IMAGE; + protected static BufferedImage SERVICETASK_IMAGE; + protected static BufferedImage RECEIVETASK_IMAGE; + protected static BufferedImage SENDTASK_IMAGE; + protected static BufferedImage MANUALTASK_IMAGE; + protected static BufferedImage BUSINESS_RULE_TASK_IMAGE; + protected static BufferedImage SHELL_TASK_IMAGE; + protected static BufferedImage MULE_TASK_IMAGE; + protected static BufferedImage CAMEL_TASK_IMAGE; + + protected static BufferedImage TIMER_IMAGE; + protected static BufferedImage COMPENSATE_THROW_IMAGE; + protected static BufferedImage COMPENSATE_CATCH_IMAGE; + protected static BufferedImage ERROR_THROW_IMAGE; + protected static BufferedImage ERROR_CATCH_IMAGE; + protected static BufferedImage MESSAGE_THROW_IMAGE; + protected static BufferedImage MESSAGE_CATCH_IMAGE; + protected static BufferedImage SIGNAL_CATCH_IMAGE; + protected static BufferedImage SIGNAL_THROW_IMAGE; + + protected int canvasWidth = -1; + protected int canvasHeight = -1; + protected int minX = -1; + protected int minY = -1; + protected BufferedImage processDiagram; + protected Graphics2D g; + protected FontMetrics fontMetrics; + protected boolean closed; + protected ClassLoader customClassLoader; + protected String activityFontName = "Arial"; + protected String labelFontName = "Arial"; + protected String annotationFontName = "Arial"; + + /** + * Creates an empty canvas with given width and height. + *

+ * Allows to specify minimal boundaries on the left and upper side of the + * canvas. This is useful for diagrams that have white space there. + * Everything beneath these minimum values will be cropped. + * It's also possible to pass a specific font name and a class loader for the icon images. + */ + public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + + this.canvasWidth = width; + this.canvasHeight = height; + this.minX = minX; + this.minY = minY; + if (activityFontName != null) { + this.activityFontName = activityFontName; + } + if (labelFontName != null) { + this.labelFontName = labelFontName; + } + if (annotationFontName != null) { + this.annotationFontName = annotationFontName; + } + this.customClassLoader = customClassLoader; + + initialize(imageType); + } + + /** + * Creates an empty canvas with given width and height. + *

+ * Allows to specify minimal boundaries on the left and upper side of the + * canvas. This is useful for diagrams that have white space there (eg + * Signavio). Everything beneath these minimum values will be cropped. + * + * @param minX Hint that will be used when generating the image. Parts that fall + * below minX on the horizontal scale will be cropped. + * @param minY Hint that will be used when generating the image. Parts that fall + * below minX on the horizontal scale will be cropped. + */ + public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType) { + this.canvasWidth = width; + this.canvasHeight = height; + this.minX = minX; + this.minY = minY; + + initialize(imageType); + } + + public void initialize(String imageType) { + if ("png".equalsIgnoreCase(imageType)) { + this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB); + } else { + this.processDiagram = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB); + } + + this.g = processDiagram.createGraphics(); + if ("png".equalsIgnoreCase(imageType) == false) { + this.g.setBackground(new Color(255, 255, 255, 0)); + this.g.clearRect(0, 0, canvasWidth, canvasHeight); + } + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setPaint(Color.black); + + Font font = new Font(activityFontName, Font.BOLD, FONT_SIZE); + g.setFont(font); + this.fontMetrics = g.getFontMetrics(); + + LABEL_FONT = new Font(labelFontName, Font.ITALIC, 10); + ANNOTATION_FONT = new Font(annotationFontName, Font.PLAIN, FONT_SIZE); + + try { + USERTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/userTask.png", customClassLoader)); + SCRIPTTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/scriptTask.png", customClassLoader)); + SERVICETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/serviceTask.png", customClassLoader)); + RECEIVETASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/receiveTask.png", customClassLoader)); + SENDTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/sendTask.png", customClassLoader)); + MANUALTASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/manualTask.png", customClassLoader)); + BUSINESS_RULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/businessRuleTask.png", customClassLoader)); + SHELL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/shellTask.png", customClassLoader)); + CAMEL_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/camelTask.png", customClassLoader)); + MULE_TASK_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/muleTask.png", customClassLoader)); + + TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/timer.png", customClassLoader)); + COMPENSATE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/compensate-throw.png", customClassLoader)); + COMPENSATE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/compensate.png", customClassLoader)); + ERROR_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/error-throw.png", customClassLoader)); + ERROR_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/error.png", customClassLoader)); + MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/message-throw.png", customClassLoader)); + MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/message.png", customClassLoader)); + SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/signal-throw.png", customClassLoader)); + SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/activiti/icons/signal.png", customClassLoader)); + } catch (IOException e) { + LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage()); + } + } + + /** + * Generates an image of what currently is drawn on the canvas. + *

+ * Throws an {@link ActivitiException} when {@link #close()} is already + * called. + */ + public InputStream generateImage(String imageType) { + if (closed) { + throw new ActivitiImageException("ProcessDiagramGenerator already closed"); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + ImageIO.write(processDiagram, imageType, out); + + } catch (IOException e) { + throw new ActivitiImageException("Error while generating process image", e); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException ignore) { + // Exception is silently ignored + } + } + return new ByteArrayInputStream(out.toByteArray()); + } + + /** + * Generates an image of what currently is drawn on the canvas. + *

+ * Throws an {@link ActivitiException} when {@link #close()} is already + * called. + */ + public BufferedImage generateBufferedImage(String imageType) { + if (closed) { + throw new ActivitiImageException("ProcessDiagramGenerator already closed"); + } + + // Try to remove white space + minX = (minX <= 5) ? 5 : minX; + minY = (minY <= 5) ? 5 : minY; + BufferedImage imageToSerialize = processDiagram; + if (minX >= 0 && minY >= 0) { + imageToSerialize = processDiagram.getSubimage(minX - 5, minY - 5, canvasWidth - minX + 5, canvasHeight - minY + 5); + } + return imageToSerialize; + } + + /** + * Closes the canvas which dissallows further drawing and releases graphical + * resources. + */ + public void close() { + g.dispose(); + closed = true; + } + + public void drawNoneStartEvent(GraphicInfo graphicInfo) { + drawStartEvent(graphicInfo, null, 1.0); + } + + public void drawTimerStartEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawStartEvent(graphicInfo, TIMER_IMAGE, scaleFactor); + } + + public void drawSignalStartEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawStartEvent(graphicInfo, SIGNAL_CATCH_IMAGE, scaleFactor); + } + + public void drawMessageStartEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawStartEvent(graphicInfo, MESSAGE_CATCH_IMAGE, scaleFactor); + } + + public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) { + Paint originalPaint = g.getPaint(); + g.setPaint(EVENT_COLOR); + Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + g.fill(circle); + g.setPaint(EVENT_BORDER_COLOR); + g.draw(circle); + g.setPaint(originalPaint); + if (image != null) { + // calculate coordinates to center image + int imageX = (int) Math.round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / 2 * scaleFactor)); + int imageY = (int) Math.round(graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / 2 * scaleFactor)); + g.drawImage(image, imageX, imageY, + (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), null); + } + + } + + public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + g.setPaint(EVENT_COLOR); + Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + g.fill(circle); + g.setPaint(EVENT_BORDER_COLOR); + if (scaleFactor == 1.0) { + g.setStroke(END_EVENT_STROKE); + } else { + g.setStroke(new BasicStroke(2.0f)); + } + g.draw(circle); + g.setStroke(originalStroke); + g.setPaint(originalPaint); + } + + public void drawErrorEndEvent(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawErrorEndEvent(graphicInfo, scaleFactor); + if (scaleFactor == 1.0) { + drawLabel(name, graphicInfo); + } + } + + public void drawErrorEndEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawNoneEndEvent(graphicInfo, scaleFactor); + g.drawImage(ERROR_THROW_IMAGE, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)), + (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)), + (int) (ERROR_THROW_IMAGE.getWidth() / scaleFactor), + (int) (ERROR_THROW_IMAGE.getHeight() / scaleFactor), null); + } + + public void drawErrorStartEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawNoneStartEvent(graphicInfo); + g.drawImage(ERROR_CATCH_IMAGE, (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 4)), + (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 4)), + (int) (ERROR_CATCH_IMAGE.getWidth() / scaleFactor), + (int) (ERROR_CATCH_IMAGE.getHeight() / scaleFactor), null); + } + + public void drawCatchingEvent(GraphicInfo graphicInfo, boolean isInterrupting, + BufferedImage image, String eventType, double scaleFactor) { + + // event circles + Ellipse2D outerCircle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + int innerCircleSize = (int) (4 / scaleFactor); + if (innerCircleSize == 0) { + innerCircleSize = 1; + } + int innerCircleX = (int) graphicInfo.getX() + innerCircleSize; + int innerCircleY = (int) graphicInfo.getY() + innerCircleSize; + int innerCircleWidth = (int) graphicInfo.getWidth() - (2 * innerCircleSize); + int innerCircleHeight = (int) graphicInfo.getHeight() - (2 * innerCircleSize); + Ellipse2D innerCircle = new Ellipse2D.Double(innerCircleX, innerCircleY, innerCircleWidth, innerCircleHeight); + + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + g.setPaint(EVENT_COLOR); + g.fill(outerCircle); + + g.setPaint(EVENT_BORDER_COLOR); + if (isInterrupting == false) + g.setStroke(NON_INTERRUPTING_EVENT_STROKE); + g.draw(outerCircle); + g.setStroke(originalStroke); + g.setPaint(originalPaint); + g.draw(innerCircle); + + if (image != null) { + // calculate coordinates to center image + int imageX = (int) (graphicInfo.getX() + (graphicInfo.getWidth() / 2) - (image.getWidth() / 2 * scaleFactor)); + int imageY = (int) (graphicInfo.getY() + (graphicInfo.getHeight() / 2) - (image.getHeight() / 2 * scaleFactor)); + if (scaleFactor == 1.0 && "timer".equals(eventType)) { + // move image one pixel to center timer image + imageX++; + imageY++; + } + g.drawImage(image, imageX, imageY, (int) (image.getWidth() / scaleFactor), + (int) (image.getHeight() / scaleFactor), null); + } + } + + public void drawCatchingCompensateEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingCompensateEvent(graphicInfo, isInterrupting, scaleFactor); + drawLabel(name, graphicInfo); + } + + public void drawCatchingCompensateEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingEvent(graphicInfo, isInterrupting, COMPENSATE_CATCH_IMAGE, "compensate", scaleFactor); + } + + public void drawCatchingTimerEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingTimerEvent(graphicInfo, isInterrupting, scaleFactor); + drawLabel(name, graphicInfo); + } + + public void drawCatchingTimerEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingEvent(graphicInfo, isInterrupting, TIMER_IMAGE, "timer", scaleFactor); + } + + public void drawCatchingErrorEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingErrorEvent(graphicInfo, isInterrupting, scaleFactor); + drawLabel(name, graphicInfo); + } + + public void drawCatchingErrorEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingEvent(graphicInfo, isInterrupting, ERROR_CATCH_IMAGE, "error", scaleFactor); + } + + public void drawCatchingSignalEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingSignalEvent(graphicInfo, isInterrupting, scaleFactor); + drawLabel(name, graphicInfo); + } + + public void drawCatchingSignalEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingEvent(graphicInfo, isInterrupting, SIGNAL_CATCH_IMAGE, "signal", scaleFactor); + } + + public void drawCatchingMessageEvent(GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingEvent(graphicInfo, isInterrupting, MESSAGE_CATCH_IMAGE, "message", scaleFactor); + } + + public void drawCatchingMessageEvent(String name, GraphicInfo graphicInfo, boolean isInterrupting, double scaleFactor) { + drawCatchingEvent(graphicInfo, isInterrupting, MESSAGE_CATCH_IMAGE, "message", scaleFactor); + drawLabel(name, graphicInfo); + } + + public void drawThrowingCompensateEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawCatchingEvent(graphicInfo, true, COMPENSATE_THROW_IMAGE, "compensate", scaleFactor); + } + + public void drawThrowingSignalEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawCatchingEvent(graphicInfo, true, SIGNAL_THROW_IMAGE, "signal", scaleFactor); + } + + public void drawThrowingNoneEvent(GraphicInfo graphicInfo, double scaleFactor) { + drawCatchingEvent(graphicInfo, true, null, "none", scaleFactor); + } + + public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional, double scaleFactor) { + drawSequenceflow(srcX, srcY, targetX, targetY, conditional, false, scaleFactor); + } + + public void drawSequenceflow(int srcX, int srcY, int targetX, int targetY, boolean conditional, boolean highLighted, double scaleFactor) { + Paint originalPaint = g.getPaint(); + if (highLighted) + g.setPaint(HIGHLIGHT_COLOR); + + Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY); + g.draw(line); + drawArrowHead(line, scaleFactor); + + if (conditional) { + drawConditionalSequenceFlowIndicator(line, scaleFactor); + } + + if (highLighted) + g.setPaint(originalPaint); + } + + public void drawAssociation(int[] xPoints, int[] yPoints, AssociationDirection associationDirection, boolean highLighted, double scaleFactor) { + boolean conditional = false, isDefault = false; + drawConnection(xPoints, yPoints, conditional, isDefault, "association", associationDirection, highLighted, scaleFactor); + } + + public void drawSequenceflow(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, boolean highLighted, double scaleFactor) { + drawConnection(xPoints, yPoints, conditional, isDefault, "sequenceFlow", AssociationDirection.ONE, highLighted, scaleFactor); + } + + public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, String connectionType, + AssociationDirection associationDirection, boolean highLighted, double scaleFactor) { + + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(CONNECTION_COLOR); + if (connectionType.equals("association")) { + g.setStroke(ASSOCIATION_STROKE); + } else if (highLighted) { + g.setPaint(HIGHLIGHT_HISTORY_COLOR); + g.setStroke(HIGHLIGHT_FLOW_STROKE); + } + + for (int i = 1; i < xPoints.length; i++) { + Integer sourceX = xPoints[i - 1]; + Integer sourceY = yPoints[i - 1]; + Integer targetX = xPoints[i]; + Integer targetY = yPoints[i]; + Line2D.Double line = new Line2D.Double(sourceX, sourceY, targetX, targetY); + g.draw(line); + } + + if (isDefault) { + Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]); + drawDefaultSequenceFlowIndicator(line, scaleFactor); + } + + if (conditional) { + Line2D.Double line = new Line2D.Double(xPoints[0], yPoints[0], xPoints[1], yPoints[1]); + drawConditionalSequenceFlowIndicator(line, scaleFactor); + } + + if (associationDirection.equals(AssociationDirection.ONE) || associationDirection.equals(AssociationDirection.BOTH)) { + Line2D.Double line = new Line2D.Double(xPoints[xPoints.length - 2], yPoints[xPoints.length - 2], xPoints[xPoints.length - 1], yPoints[xPoints.length - 1]); + drawArrowHead(line, scaleFactor); + } + if (associationDirection.equals(AssociationDirection.BOTH)) { + Line2D.Double line = new Line2D.Double(xPoints[1], yPoints[1], xPoints[0], yPoints[0]); + drawArrowHead(line, scaleFactor); + } + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional, double scaleFactor) { + drawSequenceflowWithoutArrow(srcX, srcY, targetX, targetY, conditional, false, scaleFactor); + } + + public void drawSequenceflowWithoutArrow(int srcX, int srcY, int targetX, int targetY, boolean conditional, boolean highLighted, double scaleFactor) { + Paint originalPaint = g.getPaint(); + if (highLighted) + g.setPaint(HIGHLIGHT_COLOR); + + Line2D.Double line = new Line2D.Double(srcX, srcY, targetX, targetY); + g.draw(line); + + if (conditional) { + drawConditionalSequenceFlowIndicator(line, scaleFactor); + } + + if (highLighted) + g.setPaint(originalPaint); + } + + public void drawArrowHead(Line2D.Double line, double scaleFactor) { + int doubleArrowWidth = (int) (2 * ARROW_WIDTH / scaleFactor); + if (doubleArrowWidth == 0) { + doubleArrowWidth = 2; + } + Polygon arrowHead = new Polygon(); + arrowHead.addPoint(0, 0); + int arrowHeadPoint = (int) (-ARROW_WIDTH / scaleFactor); + if (arrowHeadPoint == 0) { + arrowHeadPoint = -1; + } + arrowHead.addPoint(arrowHeadPoint, -doubleArrowWidth); + arrowHeadPoint = (int) (ARROW_WIDTH / scaleFactor); + if (arrowHeadPoint == 0) { + arrowHeadPoint = 1; + } + arrowHead.addPoint(arrowHeadPoint, -doubleArrowWidth); + + AffineTransform transformation = new AffineTransform(); + transformation.setToIdentity(); + double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + transformation.translate(line.x2, line.y2); + transformation.rotate((angle - Math.PI / 2d)); + + AffineTransform originalTransformation = g.getTransform(); + g.setTransform(transformation); + g.fill(arrowHead); + g.setTransform(originalTransformation); + } + + public void drawDefaultSequenceFlowIndicator(Line2D.Double line, double scaleFactor) { + double length = DEFAULT_INDICATOR_WIDTH / scaleFactor, halfOfLength = length / 2, f = 8; + Line2D.Double defaultIndicator = new Line2D.Double(-halfOfLength, 0, halfOfLength, 0); + + double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + double dx = f * Math.cos(angle), dy = f * Math.sin(angle), + x1 = line.x1 + dx, y1 = line.y1 + dy; + + AffineTransform transformation = new AffineTransform(); + transformation.setToIdentity(); + transformation.translate(x1, y1); + transformation.rotate((angle - 3 * Math.PI / 4)); + + AffineTransform originalTransformation = g.getTransform(); + g.setTransform(transformation); + g.draw(defaultIndicator); + + g.setTransform(originalTransformation); + } + + public void drawConditionalSequenceFlowIndicator(Line2D.Double line, double scaleFactor) { + if (scaleFactor > 1.0) return; + int horizontal = (int) (CONDITIONAL_INDICATOR_WIDTH * 0.7); + int halfOfHorizontal = horizontal / 2; + int halfOfVertical = CONDITIONAL_INDICATOR_WIDTH / 2; + + Polygon conditionalIndicator = new Polygon(); + conditionalIndicator.addPoint(0, 0); + conditionalIndicator.addPoint(-halfOfHorizontal, halfOfVertical); + conditionalIndicator.addPoint(0, CONDITIONAL_INDICATOR_WIDTH); + conditionalIndicator.addPoint(halfOfHorizontal, halfOfVertical); + + AffineTransform transformation = new AffineTransform(); + transformation.setToIdentity(); + double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + transformation.translate(line.x1, line.y1); + transformation.rotate((angle - Math.PI / 2d)); + + AffineTransform originalTransformation = g.getTransform(); + g.setTransform(transformation); + g.draw(conditionalIndicator); + + Paint originalPaint = g.getPaint(); + g.setPaint(CONDITIONAL_INDICATOR_COLOR); + g.fill(conditionalIndicator); + + g.setPaint(originalPaint); + g.setTransform(originalTransformation); + } + + public void drawTask(BufferedImage icon, String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(name, graphicInfo); + g.drawImage(icon, (int) (graphicInfo.getX() + ICON_PADDING / scaleFactor), + (int) (graphicInfo.getY() + ICON_PADDING / scaleFactor), + (int) (icon.getWidth() / scaleFactor), (int) (icon.getHeight() / scaleFactor), null); + } + + public void drawTask(String name, GraphicInfo graphicInfo) { + drawTask(name, graphicInfo, false); + } + + public void drawPoolOrLane(String name, GraphicInfo graphicInfo) { + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + g.drawRect(x, y, width, height); + + // Add the name as text, vertical + if (name != null && name.length() > 0) { + // Include some padding + int availableTextSpace = height - 6; + + // Create rotation for derived font + AffineTransform transformation = new AffineTransform(); + transformation.setToIdentity(); + transformation.rotate(270 * Math.PI / 180); + + Font currentFont = g.getFont(); + Font theDerivedFont = currentFont.deriveFont(transformation); + g.setFont(theDerivedFont); + + String truncated = fitTextToWidth(name, availableTextSpace); + int realWidth = fontMetrics.stringWidth(truncated); + + g.drawString(truncated, x + 2 + fontMetrics.getHeight(), 3 + y + availableTextSpace - (availableTextSpace - realWidth) / 2); + g.setFont(currentFont); + } + } + + protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder) { + Paint originalPaint = g.getPaint(); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + // Create a new gradient paint for every task box, gradient depends on x and y and is not relative + g.setPaint(TASK_BOX_COLOR); + + int arcR = 6; + if (thickBorder) + arcR = 3; + + // shape + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR); + g.fill(rect); + g.setPaint(TASK_BORDER_COLOR); + + if (thickBorder) { + Stroke originalStroke = g.getStroke(); + g.setStroke(THICK_TASK_BORDER_STROKE); + g.draw(rect); + g.setStroke(originalStroke); + } else { + g.draw(rect); + } + + g.setPaint(originalPaint); + // text + if (name != null && name.length() > 0) { + int boxWidth = width - (2 * TEXT_PADDING); + int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2; + int boxX = x + width / 2 - boxWidth / 2; + int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2; + + drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight); + } + } + + protected void drawMultilineCentredText(String text, int x, int y, int boxWidth, int boxHeight) { + drawMultilineText(text, x, y, boxWidth, boxHeight, true); + } + + protected void drawMultilineAnnotationText(String text, int x, int y, int boxWidth, int boxHeight) { + drawMultilineText(text, x, y, boxWidth, boxHeight, false); + } + + protected void drawMultilineText(String text, int x, int y, int boxWidth, int boxHeight, boolean centered) { + // Create an attributed string based in input text + AttributedString attributedString = new AttributedString(text); + attributedString.addAttribute(TextAttribute.FONT, g.getFont()); + attributedString.addAttribute(TextAttribute.FOREGROUND, Color.black); + + AttributedCharacterIterator characterIterator = attributedString.getIterator(); + + int currentHeight = 0; + // Prepare a list of lines of text we'll be drawing + List layouts = new ArrayList(); + String lastLine = null; + + LineBreakMeasurer measurer = new LineBreakMeasurer(characterIterator, g.getFontRenderContext()); + + TextLayout layout = null; + while (measurer.getPosition() < characterIterator.getEndIndex() && currentHeight <= boxHeight) { + + int previousPosition = measurer.getPosition(); + + // Request next layout + layout = measurer.nextLayout(boxWidth); + + int height = ((Float) (layout.getDescent() + layout.getAscent() + layout.getLeading())).intValue(); + + if (currentHeight + height > boxHeight) { + // The line we're about to add should NOT be added anymore, append three dots to previous one instead + // to indicate more text is truncated + if (!layouts.isEmpty()) { + layouts.remove(layouts.size() - 1); + + if (lastLine.length() >= 4) { + lastLine = lastLine.substring(0, lastLine.length() - 4) + "..."; + } + layouts.add(new TextLayout(lastLine, g.getFont(), g.getFontRenderContext())); + } + break; + } else { + layouts.add(layout); + lastLine = text.substring(previousPosition, measurer.getPosition()); + currentHeight += height; + } + } + + + int currentY = y + (centered ? ((boxHeight - currentHeight) / 2) : 0); + int currentX = 0; + + // Actually draw the lines + for (TextLayout textLayout : layouts) { + + currentY += textLayout.getAscent(); + currentX = x + (centered ? ((boxWidth - ((Double) textLayout.getBounds().getWidth()).intValue()) / 2) : 0); + + textLayout.draw(g, currentX, currentY); + currentY += textLayout.getDescent() + textLayout.getLeading(); + } + + } + + + protected String fitTextToWidth(String original, int width) { + String text = original; + + // remove length for "..." + int maxWidth = width - 10; + + while (fontMetrics.stringWidth(text + "...") > maxWidth && text.length() > 0) { + text = text.substring(0, text.length() - 1); + } + + if (!text.equals(original)) { + text = text + "..."; + } + + return text; + } + + public void drawUserTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(USERTASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawScriptTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(SCRIPTTASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawServiceTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(SERVICETASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawReceiveTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(RECEIVETASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawSendTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(SENDTASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawManualTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(MANUALTASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawBusinessRuleTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(BUSINESS_RULE_TASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawCamelTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(CAMEL_TASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawMuleTask(String name, GraphicInfo graphicInfo, double scaleFactor) { + drawTask(MULE_TASK_IMAGE, name, graphicInfo, scaleFactor); + } + + public void drawExpandedSubProcess(String name, GraphicInfo graphicInfo, Boolean isTriggeredByEvent, double scaleFactor) { + RoundRectangle2D rect = new RoundRectangle2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight(), 8, 8); + + // Use different stroke (dashed) + if (isTriggeredByEvent) { + Stroke originalStroke = g.getStroke(); + g.setStroke(EVENT_SUBPROCESS_STROKE); + g.draw(rect); + g.setStroke(originalStroke); + } else { + Paint originalPaint = g.getPaint(); + g.setPaint(SUBPROCESS_BOX_COLOR); + g.fill(rect); + g.setPaint(SUBPROCESS_BORDER_COLOR); + g.draw(rect); + g.setPaint(originalPaint); + } + + if (scaleFactor == 1.0 && name != null && !name.isEmpty()) { + String text = fitTextToWidth(name, (int) graphicInfo.getWidth()); + g.drawString(text, (int) graphicInfo.getX() + 10, (int) graphicInfo.getY() + 15); + } + } + + public void drawCollapsedSubProcess(String name, GraphicInfo graphicInfo, Boolean isTriggeredByEvent) { + drawCollapsedTask(name, graphicInfo, false); + } + + public void drawCollapsedCallActivity(String name, GraphicInfo graphicInfo) { + drawCollapsedTask(name, graphicInfo, true); + } + + protected void drawCollapsedTask(String name, GraphicInfo graphicInfo, boolean thickBorder) { + // The collapsed marker is now visualized separately + drawTask(name, graphicInfo, thickBorder); + } + + public void drawCollapsedMarker(int x, int y, int width, int height) { + // rectangle + int rectangleWidth = MARKER_WIDTH; + int rectangleHeight = MARKER_WIDTH; + Rectangle rect = new Rectangle(x + (width - rectangleWidth) / 2, y + height - rectangleHeight - 3, rectangleWidth, rectangleHeight); + g.draw(rect); + + // plus inside rectangle + Line2D.Double line = new Line2D.Double(rect.getCenterX(), rect.getY() + 2, rect.getCenterX(), rect.getMaxY() - 2); + g.draw(line); + line = new Line2D.Double(rect.getMinX() + 2, rect.getCenterY(), rect.getMaxX() - 2, rect.getCenterY()); + g.draw(line); + } + + public void drawActivityMarkers(int x, int y, int width, int height, boolean multiInstanceSequential, boolean multiInstanceParallel, boolean collapsed) { + if (collapsed) { + if (!multiInstanceSequential && !multiInstanceParallel) { + drawCollapsedMarker(x, y, width, height); + } else { + drawCollapsedMarker(x - MARKER_WIDTH / 2 - 2, y, width, height); + if (multiInstanceSequential) { + drawMultiInstanceMarker(true, x + MARKER_WIDTH / 2 + 2, y, width, height); + } else { + drawMultiInstanceMarker(false, x + MARKER_WIDTH / 2 + 2, y, width, height); + } + } + } else { + if (multiInstanceSequential) { + drawMultiInstanceMarker(true, x, y, width, height); + } else if (multiInstanceParallel) { + drawMultiInstanceMarker(false, x, y, width, height); + } + } + } + + public void drawGateway(GraphicInfo graphicInfo) { + Polygon rhombus = new Polygon(); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + rhombus.addPoint(x, y + (height / 2)); + rhombus.addPoint(x + (width / 2), y + height); + rhombus.addPoint(x + width, y + (height / 2)); + rhombus.addPoint(x + (width / 2), y); + g.draw(rhombus); + } + + public void drawParallelGateway(GraphicInfo graphicInfo, double scaleFactor) { + // rhombus + drawGateway(graphicInfo); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + if (scaleFactor == 1.0) { + // plus inside rhombus + Stroke orginalStroke = g.getStroke(); + g.setStroke(GATEWAY_TYPE_STROKE); + Line2D.Double line = new Line2D.Double(x + 10, y + height / 2, x + width - 10, y + height / 2); // horizontal + g.draw(line); + line = new Line2D.Double(x + width / 2, y + height - 10, x + width / 2, y + 10); // vertical + g.draw(line); + g.setStroke(orginalStroke); + } + } + + public void drawExclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) { + // rhombus + drawGateway(graphicInfo); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + int quarterWidth = width / 4; + int quarterHeight = height / 4; + + if (scaleFactor == 1.0) { + // X inside rhombus + Stroke orginalStroke = g.getStroke(); + g.setStroke(GATEWAY_TYPE_STROKE); + Line2D.Double line = new Line2D.Double(x + quarterWidth + 3, y + quarterHeight + 3, x + 3 * quarterWidth - 3, y + 3 * quarterHeight - 3); + g.draw(line); + line = new Line2D.Double(x + quarterWidth + 3, y + 3 * quarterHeight - 3, x + 3 * quarterWidth - 3, y + quarterHeight + 3); + g.draw(line); + g.setStroke(orginalStroke); + } + } + + public void drawInclusiveGateway(GraphicInfo graphicInfo, double scaleFactor) { + // rhombus + drawGateway(graphicInfo); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + int diameter = width / 2; + + if (scaleFactor == 1.0) { + // circle inside rhombus + Stroke orginalStroke = g.getStroke(); + g.setStroke(GATEWAY_TYPE_STROKE); + Ellipse2D.Double circle = new Ellipse2D.Double(((width - diameter) / 2) + x, ((height - diameter) / 2) + y, diameter, diameter); + g.draw(circle); + g.setStroke(orginalStroke); + } + } + + public void drawEventBasedGateway(GraphicInfo graphicInfo, double scaleFactor) { + // rhombus + drawGateway(graphicInfo); + + if (scaleFactor == 1.0) { + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + double scale = .6; + + GraphicInfo eventInfo = new GraphicInfo(); + eventInfo.setX(x + width * (1 - scale) / 2); + eventInfo.setY(y + height * (1 - scale) / 2); + eventInfo.setWidth(width * scale); + eventInfo.setHeight(height * scale); + drawCatchingEvent(eventInfo, true, null, "eventGateway", scaleFactor); + + double r = width / 6.; + + // create pentagon (coords with respect to center) + int topX = (int) (.95 * r); // top right corner + int topY = (int) (-.31 * r); + int bottomX = (int) (.59 * r); // bottom right corner + int bottomY = (int) (.81 * r); + + int[] xPoints = new int[]{0, topX, bottomX, -bottomX, -topX}; + int[] yPoints = new int[]{-(int) r, topY, bottomY, bottomY, topY}; + Polygon pentagon = new Polygon(xPoints, yPoints, 5); + pentagon.translate(x + width / 2, y + width / 2); + + // draw + g.drawPolygon(pentagon); + } + } + + public void drawMultiInstanceMarker(boolean sequential, int x, int y, int width, int height) { + int rectangleWidth = MARKER_WIDTH; + int rectangleHeight = MARKER_WIDTH; + int lineX = x + (width - rectangleWidth) / 2; + int lineY = y + height - rectangleHeight - 3; + + Stroke orginalStroke = g.getStroke(); + g.setStroke(MULTI_INSTANCE_STROKE); + + if (sequential) { + g.draw(new Line2D.Double(lineX, lineY, lineX + rectangleWidth, lineY)); + g.draw(new Line2D.Double(lineX, lineY + rectangleHeight / 2, lineX + rectangleWidth, lineY + rectangleHeight / 2)); + g.draw(new Line2D.Double(lineX, lineY + rectangleHeight, lineX + rectangleWidth, lineY + rectangleHeight)); + } else { + g.draw(new Line2D.Double(lineX, lineY, lineX, lineY + rectangleHeight)); + g.draw(new Line2D.Double(lineX + rectangleWidth / 2, lineY, lineX + rectangleWidth / 2, lineY + rectangleHeight)); + g.draw(new Line2D.Double(lineX + rectangleWidth, lineY, lineX + rectangleWidth, lineY + rectangleHeight)); + } + + g.setStroke(orginalStroke); + } + + public void drawHighLight(int x, int y, int width, int height) { + drawHighlight(x, y, width, height, HIGHLIGHT_COLOR); + } + + public void drawHighLightHistory(int x, int y, int width, int height) { + drawHighlight(x, y, width, height, HIGHLIGHT_HISTORY_COLOR); + } + + private void drawHighlight(int x, int y, int width, int height, Color color) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(color); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 5, 5); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + public void drawTextAnnotation(String text, GraphicInfo graphicInfo) { + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + Font originalFont = g.getFont(); + Stroke originalStroke = g.getStroke(); + + g.setFont(ANNOTATION_FONT); + + Path2D path = new Path2D.Double(); + x += .5; + int lineLength = 18; + path.moveTo(x + lineLength, y); + path.lineTo(x, y); + path.lineTo(x, y + height); + path.lineTo(x + lineLength, y + height); + + path.lineTo(x + lineLength, y + height - 1); + path.lineTo(x + 1, y + height - 1); + path.lineTo(x + 1, y + 1); + path.lineTo(x + lineLength, y + 1); + path.closePath(); + + g.draw(path); + + int boxWidth = width - (2 * ANNOTATION_TEXT_PADDING); + int boxHeight = height - (2 * ANNOTATION_TEXT_PADDING); + int boxX = x + width / 2 - boxWidth / 2; + int boxY = y + height / 2 - boxHeight / 2; + + if (text != null && text.isEmpty() == false) { + drawMultilineAnnotationText(text, boxX, boxY, boxWidth, boxHeight); + } + + // restore originals + g.setFont(originalFont); + g.setStroke(originalStroke); + } + + public void drawLabel(String text, GraphicInfo graphicInfo) { + drawLabel(text, graphicInfo, true); + } + + public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) { + float interline = 1.0f; + + // text + if (text != null && text.length() > 0) { + Paint originalPaint = g.getPaint(); + Font originalFont = g.getFont(); + + g.setPaint(LABEL_COLOR); + g.setFont(LABEL_FONT); + + int wrapWidth = 100; + int textY = (int) graphicInfo.getY(); + + // TODO: use drawMultilineText() + AttributedString as = new AttributedString(text); + as.addAttribute(TextAttribute.FOREGROUND, g.getPaint()); + as.addAttribute(TextAttribute.FONT, g.getFont()); + // 设置文字颜色 + as.addAttribute(TextAttribute.BACKGROUND, Color.WHITE); + AttributedCharacterIterator aci = as.getIterator(); + FontRenderContext frc = new FontRenderContext(null, true, false); + LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc); + + while (lbm.getPosition() < text.length()) { + TextLayout tl = lbm.nextLayout(wrapWidth); + textY += tl.getAscent(); + Rectangle2D bb = tl.getBounds(); + double tX = graphicInfo.getX(); + if (centered) { + tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2); + } + // 文字居中 + int fontLength = text.length() * originalFont.getSize() + 5; + int fontLeftX; + if (fontLength >= wrapWidth) { + fontLeftX = wrapWidth / 2; + } else { + fontLeftX = fontLength / 2; + } + tl.draw(g, (float) tX - fontLeftX, textY - originalFont.getSize() / 2); + textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent(); + } + + // restore originals + g.setFont(originalFont); + g.setPaint(originalPaint); + } + } + + /** + * This method makes coordinates of connection flow better. + * + * @param sourceShapeType + * @param targetShapeType + * @param sourceGraphicInfo + * @param targetGraphicInfo + * @param graphicInfoList + */ + public List connectionPerfectionizer(SHAPE_TYPE sourceShapeType, SHAPE_TYPE targetShapeType, GraphicInfo sourceGraphicInfo, GraphicInfo targetGraphicInfo, List graphicInfoList) { + Shape shapeFirst = createShape(sourceShapeType, sourceGraphicInfo); + Shape shapeLast = createShape(targetShapeType, targetGraphicInfo); + + if (graphicInfoList != null && graphicInfoList.size() > 0) { + GraphicInfo graphicInfoFirst = graphicInfoList.get(0); + GraphicInfo graphicInfoLast = graphicInfoList.get(graphicInfoList.size() - 1); + if (shapeFirst != null) { + graphicInfoFirst.setX(shapeFirst.getBounds2D().getCenterX()); + graphicInfoFirst.setY(shapeFirst.getBounds2D().getCenterY()); + } + if (shapeLast != null) { + graphicInfoLast.setX(shapeLast.getBounds2D().getCenterX()); + graphicInfoLast.setY(shapeLast.getBounds2D().getCenterY()); + } + + Point p = null; + + if (shapeFirst != null) { + Line2D.Double lineFirst = new Line2D.Double(graphicInfoFirst.getX(), graphicInfoFirst.getY(), graphicInfoList.get(1).getX(), graphicInfoList.get(1).getY()); + p = getIntersection(shapeFirst, lineFirst); + if (p != null) { + graphicInfoFirst.setX(p.getX()); + graphicInfoFirst.setY(p.getY()); + } + } + + if (shapeLast != null) { + Line2D.Double lineLast = new Line2D.Double(graphicInfoLast.getX(), graphicInfoLast.getY(), graphicInfoList.get(graphicInfoList.size() - 2).getX(), graphicInfoList.get(graphicInfoList.size() - 2).getY()); + p = getIntersection(shapeLast, lineLast); + if (p != null) { + graphicInfoLast.setX(p.getX()); + graphicInfoLast.setY(p.getY()); + } + } + } + + return graphicInfoList; + } + + /** + * This method creates shape by type and coordinates. + * + * @param shapeType + * @param graphicInfo + * @return Shape + */ + private static Shape createShape(SHAPE_TYPE shapeType, GraphicInfo graphicInfo) { + if (SHAPE_TYPE.Rectangle.equals(shapeType)) { + // source is rectangle + return new Rectangle2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight()); + } else if (SHAPE_TYPE.Rhombus.equals(shapeType)) { + // source is rhombus + Path2D.Double rhombus = new Path2D.Double(); + rhombus.moveTo(graphicInfo.getX(), graphicInfo.getY() + graphicInfo.getHeight() / 2); + rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2, graphicInfo.getY() + graphicInfo.getHeight()); + rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth(), graphicInfo.getY() + graphicInfo.getHeight() / 2); + rhombus.lineTo(graphicInfo.getX() + graphicInfo.getWidth() / 2, graphicInfo.getY()); + rhombus.lineTo(graphicInfo.getX(), graphicInfo.getY() + graphicInfo.getHeight() / 2); + rhombus.closePath(); + return rhombus; + } else if (SHAPE_TYPE.Ellipse.equals(shapeType)) { + // source is ellipse + return new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), graphicInfo.getWidth(), graphicInfo.getHeight()); + } else { + // unknown source element, just do not correct coordinates + } + return null; + } + + /** + * This method returns intersection point of shape border and line. + * + * @param shape + * @param line + * @return Point + */ + private static Point getIntersection(Shape shape, Line2D.Double line) { + if (shape instanceof Ellipse2D) { + return getEllipseIntersection(shape, line); + } else if (shape instanceof Rectangle2D || shape instanceof Path2D) { + return getShapeIntersection(shape, line); + } else { + // something strange + return null; + } + } + + /** + * This method calculates ellipse intersection with line + * + * @param shape Bounds of this shape used to calculate parameters of inscribed into this bounds ellipse. + * @param line + * @return Intersection point + */ + private static Point getEllipseIntersection(Shape shape, Line2D.Double line) { + double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1); + double x = shape.getBounds2D().getWidth() / 2 * Math.cos(angle) + shape.getBounds2D().getCenterX(); + double y = shape.getBounds2D().getHeight() / 2 * Math.sin(angle) + shape.getBounds2D().getCenterY(); + Point p = new Point(); + p.setLocation(x, y); + return p; + } + + /** + * This method calculates shape intersection with line. + * + * @param shape + * @param line + * @return Intersection point + */ + private static Point getShapeIntersection(Shape shape, Line2D.Double line) { + PathIterator it = shape.getPathIterator(null); + double[] coords = new double[6]; + double[] pos = new double[2]; + Line2D.Double l = new Line2D.Double(); + while (!it.isDone()) { + int type = it.currentSegment(coords); + switch (type) { + case PathIterator.SEG_MOVETO: + pos[0] = coords[0]; + pos[1] = coords[1]; + break; + case PathIterator.SEG_LINETO: + l = new Line2D.Double(pos[0], pos[1], coords[0], coords[1]); + if (line.intersectsLine(l)) { + return getLinesIntersection(line, l); + } + pos[0] = coords[0]; + pos[1] = coords[1]; + break; + case PathIterator.SEG_CLOSE: + break; + default: + // whatever + } + it.next(); + } + return null; + } + + /** + * This method calculates intersections of two lines. + * + * @param a Line 1 + * @param b Line 2 + * @return Intersection point + */ + private static Point getLinesIntersection(Line2D a, Line2D b) { + double d = (a.getX1() - a.getX2()) * (b.getY2() - b.getY1()) - (a.getY1() - a.getY2()) * (b.getX2() - b.getX1()); + double da = (a.getX1() - b.getX1()) * (b.getY2() - b.getY1()) - (a.getY1() - b.getY1()) * (b.getX2() - b.getX1()); + // double db = (a.getX1()-a.getX2())*(a.getY1()-b.getY1()) - (a.getY1()-a.getY2())*(a.getX1()-b.getX1()); + double ta = da / d; + // double tb = db/d; + Point p = new Point(); + p.setLocation(a.getX1() + ta * (a.getX2() - a.getX1()), a.getY1() + ta * (a.getY2() - a.getY1())); + return p; + } +} diff --git a/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/CustomProcessDiagramGenerator.java b/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/CustomProcessDiagramGenerator.java new file mode 100644 index 00000000..f4d395ab --- /dev/null +++ b/module-activiti/src/main/java/ink/wgink/module/activiti/service/activiti/impl/CustomProcessDiagramGenerator.java @@ -0,0 +1,935 @@ +package ink.wgink.module.activiti.service.activiti.impl; + +import org.activiti.bpmn.model.Process; +import org.activiti.bpmn.model.*; +import org.activiti.image.ProcessDiagramGenerator; + +import java.awt.image.BufferedImage; +import java.io.InputStream; +import java.util.*; + +/** + * 自定义流程图生成器 + */ +public class CustomProcessDiagramGenerator implements ProcessDiagramGenerator { + + protected Map, ActivityDrawInstruction> activityDrawInstructions = new HashMap, ActivityDrawInstruction>(); + protected Map, ArtifactDrawInstruction> artifactDrawInstructions = new HashMap, ArtifactDrawInstruction>(); + + public CustomProcessDiagramGenerator() { + this(1.0); + } + + // The instructions on how to draw a certain construct is + // created statically and stored in a map for performance. + public CustomProcessDiagramGenerator(final double scaleFactor) { + // start event + activityDrawInstructions.put(StartEvent.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + StartEvent startEvent = (StartEvent) flowNode; + if (startEvent.getEventDefinitions() != null && !startEvent.getEventDefinitions().isEmpty()) { + EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0); + if (eventDefinition instanceof TimerEventDefinition) { + processDiagramCanvas.drawTimerStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof ErrorEventDefinition) { + processDiagramCanvas.drawErrorStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof SignalEventDefinition) { + processDiagramCanvas.drawSignalStartEvent(graphicInfo, scaleFactor); + } else if (eventDefinition instanceof MessageEventDefinition) { + processDiagramCanvas.drawMessageStartEvent(graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawNoneStartEvent(graphicInfo); + } + } else { + processDiagramCanvas.drawNoneStartEvent(graphicInfo); + } + } + }); + + // signal catch + activityDrawInstructions.put(IntermediateCatchEvent.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + IntermediateCatchEvent intermediateCatchEvent = (IntermediateCatchEvent) flowNode; + if (intermediateCatchEvent.getEventDefinitions() != null && !intermediateCatchEvent.getEventDefinitions() + .isEmpty()) { + if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) { + processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) { + processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } else if (intermediateCatchEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) { + processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, true, scaleFactor); + } + } + } + }); + + // signal throw + activityDrawInstructions.put(ThrowEvent.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + ThrowEvent throwEvent = (ThrowEvent) flowNode; + if (throwEvent.getEventDefinitions() != null && !throwEvent.getEventDefinitions().isEmpty()) { + if (throwEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) { + processDiagramCanvas.drawThrowingSignalEvent(graphicInfo, scaleFactor); + } else if (throwEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) { + processDiagramCanvas.drawThrowingCompensateEvent(graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor); + } + } else { + processDiagramCanvas.drawThrowingNoneEvent(graphicInfo, scaleFactor); + } + } + }); + + // end event + activityDrawInstructions.put(EndEvent.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + EndEvent endEvent = (EndEvent) flowNode; + if (endEvent.getEventDefinitions() != null && !endEvent.getEventDefinitions().isEmpty()) { + if (endEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) { + processDiagramCanvas.drawErrorEndEvent(flowNode.getName(), graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor); + } + } else { + processDiagramCanvas.drawNoneEndEvent(graphicInfo, scaleFactor); + } + } + }); + + // task + activityDrawInstructions.put(Task.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawTask(flowNode.getName(), graphicInfo); + } + }); + + // user task + activityDrawInstructions.put(UserTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawUserTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // script task + activityDrawInstructions.put(ScriptTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawScriptTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // service task + activityDrawInstructions.put(ServiceTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + ServiceTask serviceTask = (ServiceTask) flowNode; + if ("camel".equalsIgnoreCase(serviceTask.getType())) { + processDiagramCanvas.drawCamelTask(serviceTask.getName(), graphicInfo, scaleFactor); + } else if ("mule".equalsIgnoreCase(serviceTask.getType())) { + processDiagramCanvas.drawMuleTask(serviceTask.getName(), graphicInfo, scaleFactor); + } else { + processDiagramCanvas.drawServiceTask(serviceTask.getName(), graphicInfo, scaleFactor); + } + } + }); + + // receive task + activityDrawInstructions.put(ReceiveTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawReceiveTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // send task + activityDrawInstructions.put(SendTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawSendTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // manual task + activityDrawInstructions.put(ManualTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawManualTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // businessRuleTask task + activityDrawInstructions.put(BusinessRuleTask.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawBusinessRuleTask(flowNode.getName(), graphicInfo, scaleFactor); + } + }); + + // exclusive gateway + activityDrawInstructions.put(ExclusiveGateway.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawExclusiveGateway(graphicInfo, scaleFactor); + } + }); + + // inclusive gateway + activityDrawInstructions.put(InclusiveGateway.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawInclusiveGateway(graphicInfo, scaleFactor); + } + }); + + // parallel gateway + activityDrawInstructions.put(ParallelGateway.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawParallelGateway(graphicInfo, scaleFactor); + } + }); + + // event based gateway + activityDrawInstructions.put(EventGateway.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawEventBasedGateway(graphicInfo, scaleFactor); + } + }); + + + // Boundary timer + activityDrawInstructions.put(BoundaryEvent.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + BoundaryEvent boundaryEvent = (BoundaryEvent) flowNode; + if (boundaryEvent.getEventDefinitions() != null && !boundaryEvent.getEventDefinitions().isEmpty()) { + if (boundaryEvent.getEventDefinitions().get(0) instanceof TimerEventDefinition) { + + processDiagramCanvas.drawCatchingTimerEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (boundaryEvent.getEventDefinitions().get(0) instanceof ErrorEventDefinition) { + + processDiagramCanvas.drawCatchingErrorEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (boundaryEvent.getEventDefinitions().get(0) instanceof SignalEventDefinition) { + processDiagramCanvas.drawCatchingSignalEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (boundaryEvent.getEventDefinitions().get(0) instanceof MessageEventDefinition) { + processDiagramCanvas.drawCatchingMessageEvent(flowNode.getName(), graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + + } else if (boundaryEvent.getEventDefinitions().get(0) instanceof CompensateEventDefinition) { + processDiagramCanvas.drawCatchingCompensateEvent(graphicInfo, boundaryEvent.isCancelActivity(), scaleFactor); + } + } + + } + }); + + // subprocess + activityDrawInstructions.put(SubProcess.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, false); + } else { + processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, false, scaleFactor); + } + } + }); + + // Event subprocess + activityDrawInstructions.put(EventSubProcess.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (graphicInfo.getExpanded() != null && !graphicInfo.getExpanded()) { + processDiagramCanvas.drawCollapsedSubProcess(flowNode.getName(), graphicInfo, true); + } else { + processDiagramCanvas.drawExpandedSubProcess(flowNode.getName(), graphicInfo, true, scaleFactor); + } + } + }); + + // call activity + activityDrawInstructions.put(CallActivity.class, new ActivityDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + processDiagramCanvas.drawCollapsedCallActivity(flowNode.getName(), graphicInfo); + } + }); + + // text annotation + artifactDrawInstructions.put(TextAnnotation.class, new ArtifactDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(artifact.getId()); + TextAnnotation textAnnotation = (TextAnnotation) artifact; + processDiagramCanvas.drawTextAnnotation(textAnnotation.getText(), graphicInfo); + } + }); + + // association + artifactDrawInstructions.put(Association.class, new ArtifactDrawInstruction() { + + public void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) { + Association association = (Association) artifact; + String sourceRef = association.getSourceRef(); + String targetRef = association.getTargetRef(); + + // source and target can be instance of FlowElement or Artifact + BaseElement sourceElement = bpmnModel.getFlowElement(sourceRef); + BaseElement targetElement = bpmnModel.getFlowElement(targetRef); + if (sourceElement == null) { + sourceElement = bpmnModel.getArtifact(sourceRef); + } + if (targetElement == null) { + targetElement = bpmnModel.getArtifact(targetRef); + } + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId()); + graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList); + int xPoints[] = new int[graphicInfoList.size()]; + int yPoints[] = new int[graphicInfoList.size()]; + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + } + + AssociationDirection associationDirection = association.getAssociationDirection(); + processDiagramCanvas.drawAssociation(xPoints, yPoints, associationDirection, false, scaleFactor); + } + }); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, List highLightedFlows, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) { + + return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, + activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor).generateImage(imageType); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, List highLightedFlows) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, 1.0); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, double scaleFactor) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList()); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, List highLightedActivities, double scaleFactor) { + return generateDiagram(bpmnModel, imageType, highLightedActivities, Collections.emptyList(), scaleFactor); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + + return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(), + activityFontName, labelFontName, annotationFontName, customClassLoader, 1.0); + } + + public InputStream generateDiagram(BpmnModel bpmnModel, String imageType, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) { + + return generateDiagram(bpmnModel, imageType, Collections.emptyList(), Collections.emptyList(), + activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor); + } + + public InputStream generatePngDiagram(BpmnModel bpmnModel) { + return generatePngDiagram(bpmnModel, 1.0); + } + + public InputStream generatePngDiagram(BpmnModel bpmnModel, double scaleFactor) { + return generateDiagram(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor); + } + + public InputStream generateJpgDiagram(BpmnModel bpmnModel) { + return generateJpgDiagram(bpmnModel, 1.0); + } + + public InputStream generateJpgDiagram(BpmnModel bpmnModel, double scaleFactor) { + return generateDiagram(bpmnModel, "jpg", Collections.emptyList(), Collections.emptyList()); + } + + public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, List highLightedActivities, List highLightedFlows, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) { + + return generateProcessDiagram(bpmnModel, imageType, highLightedActivities, highLightedFlows, + activityFontName, labelFontName, annotationFontName, customClassLoader, scaleFactor).generateBufferedImage(imageType); + } + + public BufferedImage generateImage(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, double scaleFactor) { + + return generateImage(bpmnModel, imageType, highLightedActivities, highLightedFlows, null, null, null, null, scaleFactor); + } + + public BufferedImage generatePngImage(BpmnModel bpmnModel, double scaleFactor) { + return generateImage(bpmnModel, "png", Collections.emptyList(), Collections.emptyList(), scaleFactor); + } + + protected CustomProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor) { + + prepareBpmnModel(bpmnModel); + + CustomProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); + + // Draw pool shape, if process is participant in collaboration + for (Pool pool : bpmnModel.getPools()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); + processDiagramCanvas.drawPoolOrLane(pool.getName(), graphicInfo); + } + + // Draw lanes + for (Process process : bpmnModel.getProcesses()) { + for (Lane lane : process.getLanes()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(lane.getId()); + processDiagramCanvas.drawPoolOrLane(lane.getName(), graphicInfo); + } + } + + // Draw activities and their sequence-flows + for (FlowNode flowNode : bpmnModel.getProcesses().get(0).findFlowElementsOfType(FlowNode.class)) { + drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor); + } + + for (Process process : bpmnModel.getProcesses()) { + for (FlowNode flowNode : process.findFlowElementsOfType(FlowNode.class)) { + drawActivity(processDiagramCanvas, bpmnModel, flowNode, highLightedActivities, highLightedFlows, scaleFactor); + } + } + + // Draw artifacts + for (Process process : bpmnModel.getProcesses()) { + + for (Artifact artifact : process.getArtifacts()) { + drawArtifact(processDiagramCanvas, bpmnModel, artifact); + } + + List subProcesses = process.findFlowElementsOfType(SubProcess.class, true); + if (subProcesses != null) { + for (SubProcess subProcess : subProcesses) { + for (Artifact subProcessArtifact : subProcess.getArtifacts()) { + drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact); + } + } + } + } + + return processDiagramCanvas; + } + + protected void prepareBpmnModel(BpmnModel bpmnModel) { + + // Need to make sure all elements have positive x and y. + // Check all graphicInfo and update the elements accordingly + + List allGraphicInfos = new ArrayList(); + if (bpmnModel.getLocationMap() != null) { + allGraphicInfos.addAll(bpmnModel.getLocationMap().values()); + } + if (bpmnModel.getLabelLocationMap() != null) { + allGraphicInfos.addAll(bpmnModel.getLabelLocationMap().values()); + } + if (bpmnModel.getFlowLocationMap() != null) { + for (List flowGraphicInfos : bpmnModel.getFlowLocationMap().values()) { + allGraphicInfos.addAll(flowGraphicInfos); + } + } + + if (allGraphicInfos.size() > 0) { + + boolean needsTranslationX = false; + boolean needsTranslationY = false; + + double lowestX = 0.0; + double lowestY = 0.0; + + // Collect lowest x and y + for (GraphicInfo graphicInfo : allGraphicInfos) { + + double x = graphicInfo.getX(); + double y = graphicInfo.getY(); + + if (x < lowestX) { + needsTranslationX = true; + lowestX = x; + } + if (y < lowestY) { + needsTranslationY = true; + lowestY = y; + } + + } + + // Update all graphicInfo objects + if (needsTranslationX || needsTranslationY) { + + double translationX = Math.abs(lowestX); + double translationY = Math.abs(lowestY); + + for (GraphicInfo graphicInfo : allGraphicInfos) { + if (needsTranslationX) { + graphicInfo.setX(graphicInfo.getX() + translationX); + } + if (needsTranslationY) { + graphicInfo.setY(graphicInfo.getY() + translationY); + } + } + } + + } + + } + + protected void drawActivity(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, + FlowNode flowNode, List highLightedActivities, List highLightedFlows, double scaleFactor) { + + ActivityDrawInstruction drawInstruction = activityDrawInstructions.get(flowNode.getClass()); + if (drawInstruction != null) { + + drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode); + + // Gather info on the multi instance marker + boolean multiInstanceSequential = false, multiInstanceParallel = false, collapsed = false; + if (flowNode instanceof Activity) { + Activity activity = (Activity) flowNode; + MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics(); + if (multiInstanceLoopCharacteristics != null) { + multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential(); + multiInstanceParallel = !multiInstanceSequential; + } + } + + // Gather info on the collapsed marker + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (flowNode instanceof SubProcess) { + collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded(); + } else if (flowNode instanceof CallActivity) { + collapsed = true; + } + + if (scaleFactor == 1.0) { + // Actually draw the markers + processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), + multiInstanceSequential, multiInstanceParallel, collapsed); + } + + // Draw highlighted activities + if (highLightedActivities.contains(flowNode.getId())) { + drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } + // Drow highlighted history activities + if (highLightedActivities.contains("#" + flowNode.getId())) { + drawHighLightHistory(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } + } + + // Outgoing transitions of activity + for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { + boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId())); + String defaultFlow = null; + if (flowNode instanceof Activity) { + defaultFlow = ((Activity) flowNode).getDefaultFlow(); + } else if (flowNode instanceof Gateway) { + defaultFlow = ((Gateway) flowNode).getDefaultFlow(); + } + + boolean isDefault = false; + if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) { + isDefault = true; + } + boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null && !(flowNode instanceof Gateway); + + String sourceRef = sequenceFlow.getSourceRef(); + String targetRef = sequenceFlow.getTargetRef(); + FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef); + FlowElement targetElement = bpmnModel.getFlowElement(targetRef); + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); + if (graphicInfoList != null && graphicInfoList.size() > 0) { + graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, targetElement, graphicInfoList); + int xPoints[] = new int[graphicInfoList.size()]; + int yPoints[] = new int[graphicInfoList.size()]; + + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + + } + + processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, highLighted, scaleFactor); + + // Draw sequenceflow label + GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId()); + if (labelGraphicInfo != null) { + processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false); + } else { + GraphicInfo lineCenter = getLineCenter(graphicInfoList); + processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false); + } + } + } + + // Nested elements + if (flowNode instanceof FlowElementsContainer) { + for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) { + if (nestedFlowElement instanceof FlowNode) { + drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, + highLightedActivities, highLightedFlows, scaleFactor); + } + } + } + } + + /** + * This method makes coordinates of connection flow better. + * + * @param processDiagramCanvas + * @param bpmnModel + * @param sourceElement + * @param targetElement + * @param graphicInfoList + * @return + */ + protected static List connectionPerfectionizer(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, BaseElement sourceElement, BaseElement targetElement, List graphicInfoList) { + GraphicInfo sourceGraphicInfo = bpmnModel.getGraphicInfo(sourceElement.getId()); + GraphicInfo targetGraphicInfo = bpmnModel.getGraphicInfo(targetElement.getId()); + + CustomProcessDiagramCanvas.SHAPE_TYPE sourceShapeType = getShapeType(sourceElement); + CustomProcessDiagramCanvas.SHAPE_TYPE targetShapeType = getShapeType(targetElement); + + return processDiagramCanvas.connectionPerfectionizer(sourceShapeType, targetShapeType, sourceGraphicInfo, targetGraphicInfo, graphicInfoList); + } + + /** + * This method returns shape type of base element.
+ * Each element can be presented as rectangle, rhombus, or ellipse. + * + * @param baseElement + * @return CustomProcessDiagramCanvas.SHAPE_TYPE + */ + protected static CustomProcessDiagramCanvas.SHAPE_TYPE getShapeType(BaseElement baseElement) { + if (baseElement instanceof Task || baseElement instanceof Activity || baseElement instanceof TextAnnotation) { + return CustomProcessDiagramCanvas.SHAPE_TYPE.Rectangle; + } else if (baseElement instanceof Gateway) { + return CustomProcessDiagramCanvas.SHAPE_TYPE.Rhombus; + } else if (baseElement instanceof Event) { + return CustomProcessDiagramCanvas.SHAPE_TYPE.Ellipse; + } else { + // unknown source element, just do not correct coordinates + } + return null; + } + + protected static GraphicInfo getLineCenter(List graphicInfoList) { + GraphicInfo gi = new GraphicInfo(); + + int xPoints[] = new int[graphicInfoList.size()]; + int yPoints[] = new int[graphicInfoList.size()]; + + double length = 0; + double[] lengths = new double[graphicInfoList.size()]; + lengths[0] = 0; + double m; + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + + length += Math.sqrt( + Math.pow((int) graphicInfo.getX() - (int) previousGraphicInfo.getX(), 2) + + Math.pow((int) graphicInfo.getY() - (int) previousGraphicInfo.getY(), 2) + ); + lengths[i] = length; + } + m = length / 2; + int p1 = 0, p2 = 1; + for (int i = 1; i < lengths.length; i++) { + double len = lengths[i]; + p1 = i - 1; + p2 = i; + if (len > m) { + break; + } + } + + GraphicInfo graphicInfo1 = graphicInfoList.get(p1); + GraphicInfo graphicInfo2 = graphicInfoList.get(p2); + + double AB = (int) graphicInfo2.getX() - (int) graphicInfo1.getX(); + double OA = (int) graphicInfo2.getY() - (int) graphicInfo1.getY(); + double OB = lengths[p2] - lengths[p1]; + double ob = m - lengths[p1]; + double ab = AB * ob / OB; + double oa = OA * ob / OB; + + double mx = graphicInfo1.getX() + ab; + double my = graphicInfo1.getY() + oa; + + gi.setX(mx); + gi.setY(my); + return gi; + } + + protected void drawArtifact(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact) { + + ArtifactDrawInstruction drawInstruction = artifactDrawInstructions.get(artifact.getClass()); + if (drawInstruction != null) { + drawInstruction.draw(processDiagramCanvas, bpmnModel, artifact); + } + } + + private static void drawHighLight(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + } + + private static void drawHighLightHistory(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLightHistory((int) graphicInfo.getX(), (int) graphicInfo.getY(), (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + } + + protected static CustomProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + + // We need to calculate maximum values to know how big the image will be in its entirety + double minX = Double.MAX_VALUE; + double maxX = 0; + double minY = Double.MAX_VALUE; + double maxY = 0; + + for (Pool pool : bpmnModel.getPools()) { + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(pool.getId()); + minX = graphicInfo.getX(); + maxX = graphicInfo.getX() + graphicInfo.getWidth(); + minY = graphicInfo.getY(); + maxY = graphicInfo.getY() + graphicInfo.getHeight(); + } + + List flowNodes = gatherAllFlowNodes(bpmnModel); + for (FlowNode flowNode : flowNodes) { + + GraphicInfo flowNodeGraphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + + // width + if (flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth() > maxX) { + maxX = flowNodeGraphicInfo.getX() + flowNodeGraphicInfo.getWidth(); + } + if (flowNodeGraphicInfo.getX() < minX) { + minX = flowNodeGraphicInfo.getX(); + } + // height + if (flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight() > maxY) { + maxY = flowNodeGraphicInfo.getY() + flowNodeGraphicInfo.getHeight(); + } + if (flowNodeGraphicInfo.getY() < minY) { + minY = flowNodeGraphicInfo.getY(); + } + + for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); + if (graphicInfoList != null) { + for (GraphicInfo graphicInfo : graphicInfoList) { + // width + if (graphicInfo.getX() > maxX) { + maxX = graphicInfo.getX(); + } + if (graphicInfo.getX() < minX) { + minX = graphicInfo.getX(); + } + // height + if (graphicInfo.getY() > maxY) { + maxY = graphicInfo.getY(); + } + if (graphicInfo.getY() < minY) { + minY = graphicInfo.getY(); + } + } + } + } + } + + List artifacts = gatherAllArtifacts(bpmnModel); + for (Artifact artifact : artifacts) { + + GraphicInfo artifactGraphicInfo = bpmnModel.getGraphicInfo(artifact.getId()); + + if (artifactGraphicInfo != null) { + // width + if (artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth() > maxX) { + maxX = artifactGraphicInfo.getX() + artifactGraphicInfo.getWidth(); + } + if (artifactGraphicInfo.getX() < minX) { + minX = artifactGraphicInfo.getX(); + } + // height + if (artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight() > maxY) { + maxY = artifactGraphicInfo.getY() + artifactGraphicInfo.getHeight(); + } + if (artifactGraphicInfo.getY() < minY) { + minY = artifactGraphicInfo.getY(); + } + } + + List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(artifact.getId()); + if (graphicInfoList != null) { + for (GraphicInfo graphicInfo : graphicInfoList) { + // width + if (graphicInfo.getX() > maxX) { + maxX = graphicInfo.getX(); + } + if (graphicInfo.getX() < minX) { + minX = graphicInfo.getX(); + } + // height + if (graphicInfo.getY() > maxY) { + maxY = graphicInfo.getY(); + } + if (graphicInfo.getY() < minY) { + minY = graphicInfo.getY(); + } + } + } + } + + int nrOfLanes = 0; + for (Process process : bpmnModel.getProcesses()) { + for (Lane l : process.getLanes()) { + + nrOfLanes++; + + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(l.getId()); + // // width + if (graphicInfo.getX() + graphicInfo.getWidth() > maxX) { + maxX = graphicInfo.getX() + graphicInfo.getWidth(); + } + if (graphicInfo.getX() < minX) { + minX = graphicInfo.getX(); + } + // height + if (graphicInfo.getY() + graphicInfo.getHeight() > maxY) { + maxY = graphicInfo.getY() + graphicInfo.getHeight(); + } + if (graphicInfo.getY() < minY) { + minY = graphicInfo.getY(); + } + } + } + + // Special case, see https://activiti.atlassian.net/browse/ACT-1431 + if (flowNodes.isEmpty() && bpmnModel.getPools().isEmpty() && nrOfLanes == 0) { + // Nothing to show + minX = 0; + minY = 0; + } + + return new CustomProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, + imageType, activityFontName, labelFontName, annotationFontName, customClassLoader); + } + + protected static List gatherAllArtifacts(BpmnModel bpmnModel) { + List artifacts = new ArrayList(); + for (Process process : bpmnModel.getProcesses()) { + artifacts.addAll(process.getArtifacts()); + } + return artifacts; + } + + protected static List gatherAllFlowNodes(BpmnModel bpmnModel) { + List flowNodes = new ArrayList(); + for (Process process : bpmnModel.getProcesses()) { + flowNodes.addAll(gatherAllFlowNodes(process)); + } + return flowNodes; + } + + protected static List gatherAllFlowNodes(FlowElementsContainer flowElementsContainer) { + List flowNodes = new ArrayList(); + for (FlowElement flowElement : flowElementsContainer.getFlowElements()) { + if (flowElement instanceof FlowNode) { + flowNodes.add((FlowNode) flowElement); + } + if (flowElement instanceof FlowElementsContainer) { + flowNodes.addAll(gatherAllFlowNodes((FlowElementsContainer) flowElement)); + } + } + return flowNodes; + } + + public Map, ActivityDrawInstruction> getActivityDrawInstructions() { + return activityDrawInstructions; + } + + public void setActivityDrawInstructions( + Map, ActivityDrawInstruction> activityDrawInstructions) { + this.activityDrawInstructions = activityDrawInstructions; + } + + public Map, ArtifactDrawInstruction> getArtifactDrawInstructions() { + return artifactDrawInstructions; + } + + public void setArtifactDrawInstructions( + Map, ArtifactDrawInstruction> artifactDrawInstructions) { + this.artifactDrawInstructions = artifactDrawInstructions; + } + + + protected interface ActivityDrawInstruction { + void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, FlowNode flowNode); + } + + protected interface ArtifactDrawInstruction { + void draw(CustomProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, Artifact artifact); + } +}