/*******************************************************************************
* Copyright (c) 2011 Red Hat, Inc.
* All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.bpmn2.modeler.ui.features.flow;
import java.util.Iterator;
import org.eclipse.bpmn2.Activity;
import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.ComplexGateway;
import org.eclipse.bpmn2.ExclusiveGateway;
import org.eclipse.bpmn2.FlowNode;
import org.eclipse.bpmn2.InclusiveGateway;
import org.eclipse.bpmn2.SequenceFlow;
import org.eclipse.bpmn2.di.BPMNEdge;
import org.eclipse.bpmn2.modeler.core.Activator;
import org.eclipse.bpmn2.modeler.core.ModelHandler;
import org.eclipse.bpmn2.modeler.core.features.BaseElementConnectionFeatureContainer;
import org.eclipse.bpmn2.modeler.core.features.BaseElementReconnectionFeature;
import org.eclipse.bpmn2.modeler.core.features.BusinessObjectUtil;
import org.eclipse.bpmn2.modeler.core.features.MultiUpdateFeature;
import org.eclipse.bpmn2.modeler.core.features.UpdateBaseElementNameFeature;
import org.eclipse.bpmn2.modeler.core.features.flow.AbstractAddFlowFeature;
import org.eclipse.bpmn2.modeler.core.features.flow.AbstractCreateFlowFeature;
import org.eclipse.bpmn2.modeler.core.utils.StyleUtil;
import org.eclipse.bpmn2.modeler.core.utils.Tuple;
import org.eclipse.bpmn2.modeler.ui.ImageProvider;
import org.eclipse.dd.di.DiagramElement;
import org.eclipse.graphiti.features.IAddFeature;
import org.eclipse.graphiti.features.ICreateConnectionFeature;
import org.eclipse.graphiti.features.IDeleteFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.IReason;
import org.eclipse.graphiti.features.IReconnectionFeature;
import org.eclipse.graphiti.features.IUpdateFeature;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.IReconnectionContext;
import org.eclipse.graphiti.features.context.IUpdateContext;
import org.eclipse.graphiti.features.context.impl.ReconnectionContext;
import org.eclipse.graphiti.features.impl.AbstractUpdateFeature;
import org.eclipse.graphiti.features.impl.Reason;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.algorithms.styles.Color;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeService;
import org.eclipse.graphiti.util.IColorConstant;
public class SequenceFlowFeatureContainer extends BaseElementConnectionFeatureContainer {
private static final String IS_DEFAULT_FLOW_PROPERTY = "is.default.flow";
private static final String IS_CONDITIONAL_FLOW_PROPERTY = "is.conditional.flow";
private static final String DEFAULT_MARKER_PROPERTY = "default.marker";
private static final String CONDITIONAL_MARKER_PROPERTY = "conditional.marker";
@Override
public boolean canApplyTo(Object o) {
return super.canApplyTo(o) && o instanceof SequenceFlow;
}
@Override
public IAddFeature getAddFeature(IFeatureProvider fp) {
return new AbstractAddFlowFeature(fp) {
@Override
protected Class<? extends BaseElement> getBoClass() {
return SequenceFlow.class;
}
@Override
protected void createConnectionDecorators(Connection connection) {
int w = 3;
int l = 8;
ConnectionDecorator decorator = Graphiti.getPeService().createConnectionDecorator(connection, false,
1.0, true);
IGaService gaService = Graphiti.getGaService();
Polyline arrow = gaService.createPolygon(decorator, new int[] { -l, w, 0, 0, -l, -w, -l, w });
arrow.setForeground(manageColor(StyleUtil.CLASS_FOREGROUND));
arrow.setLineWidth(2);
}
@Override
protected void hook(IAddContext context, Connection connection, BaseElement element) {
setDefaultSequenceFlow(connection);
setConditionalSequenceFlow(connection);
}
};
}
@Override
public ICreateConnectionFeature getCreateConnectionFeature(IFeatureProvider fp) {
return new CreateSequenceFlowFeature(fp);
}
@Override
public IUpdateFeature getUpdateFeature(IFeatureProvider fp) {
MultiUpdateFeature multiUpdate = new MultiUpdateFeature(fp);
multiUpdate.addUpdateFeature(new UpdateDefaultSequenceFlowFeature(fp));
multiUpdate.addUpdateFeature(new UpdateConditionalSequenceFlowFeature(fp));
multiUpdate.addUpdateFeature(new UpdateBaseElementNameFeature(fp));
return multiUpdate;
}
@Override
public IReconnectionFeature getReconnectionFeature(IFeatureProvider fp) {
return new SequenceFlowReconnectionFeature(fp);
}
@Override
public IDeleteFeature getDeleteFeature(IFeatureProvider fp) {
return null;
}
public static class CreateSequenceFlowFeature extends AbstractCreateFlowFeature<FlowNode, FlowNode> {
public CreateSequenceFlowFeature(IFeatureProvider fp) {
super(fp, "Sequence Flow",
"A Sequence Flow is used to show the order that Activities will be performed in a Process");
}
@Override
protected String getStencilImageId() {
return ImageProvider.IMG_16_SEQUENCE_FLOW;
}
@Override
protected BaseElement createFlow(ModelHandler mh, FlowNode source, FlowNode target) {
SequenceFlow flow = mh.createSequenceFlow(source, target);
flow.setName("");
return flow;
}
@Override
protected Class<FlowNode> getSourceClass() {
return FlowNode.class;
}
@Override
protected Class<FlowNode> getTargetClass() {
return FlowNode.class;
}
}
private static Color manageColor(PictogramElement element, IColorConstant colorConstant) {
IPeService peService = Graphiti.getPeService();
Diagram diagram = peService.getDiagramForPictogramElement(element);
return Graphiti.getGaService().manageColor(diagram, colorConstant);
}
private static void setDefaultSequenceFlow(Connection connection) {
IPeService peService = Graphiti.getPeService();
SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(connection, SequenceFlow.class);
SequenceFlow defaultFlow = getDefaultFlow(flow.getSourceRef());
boolean isDefault = defaultFlow == null ? false : defaultFlow.equals(flow);
Tuple<ConnectionDecorator, ConnectionDecorator> decorators = getConnectionDecorators(connection);
ConnectionDecorator def = decorators.getFirst();
ConnectionDecorator cond = decorators.getSecond();
if (isDefault) {
if (cond != null) {
peService.deletePictogramElement(cond);
}
def = createDefaultConnectionDecorator(connection);
GraphicsAlgorithm ga = def.getGraphicsAlgorithm();
ga.setForeground(manageColor(connection,StyleUtil.CLASS_FOREGROUND));
} else {
if (def != null) {
peService.deletePictogramElement(def);
}
if (flow.getConditionExpression() != null && flow.getSourceRef() instanceof Activity) {
cond = createConditionalConnectionDecorator(connection);
GraphicsAlgorithm ga = cond.getGraphicsAlgorithm();
ga.setFilled(true);
ga.setForeground(manageColor(connection,StyleUtil.CLASS_FOREGROUND));
ga.setBackground(manageColor(connection,IColorConstant.WHITE));
}
}
peService.setPropertyValue(connection, IS_DEFAULT_FLOW_PROPERTY,
Boolean.toString(isDefault));
}
public static class UpdateDefaultSequenceFlowFeature extends AbstractUpdateFeature {
public UpdateDefaultSequenceFlowFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canUpdate(IUpdateContext context) {
SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(context.getPictogramElement(),
SequenceFlow.class);
boolean canUpdate = flow != null && isDefaultAttributeSupported(flow.getSourceRef());
return canUpdate;
}
@Override
public IReason updateNeeded(IUpdateContext context) {
IPeService peService = Graphiti.getPeService();
String property = peService.getPropertyValue(context.getPictogramElement(), IS_DEFAULT_FLOW_PROPERTY);
if (property == null || !canUpdate(context)) {
return Reason.createFalseReason();
}
SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(context.getPictogramElement(),
SequenceFlow.class);
SequenceFlow defaultFlow = getDefaultFlow(flow.getSourceRef());
boolean isDefault = defaultFlow == null ? false : defaultFlow.equals(flow);
boolean changed = isDefault != new Boolean(property);
return changed ? Reason.createTrueReason() : Reason.createFalseReason();
}
@Override
public boolean update(IUpdateContext context) {
Connection connection = (Connection) context.getPictogramElement();
setDefaultSequenceFlow(connection);
return true;
}
}
private static void setConditionalSequenceFlow(Connection connection) {
IPeService peService = Graphiti.getPeService();
SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(connection, SequenceFlow.class);
Tuple<ConnectionDecorator, ConnectionDecorator> decorators = getConnectionDecorators(connection);
ConnectionDecorator def = decorators.getFirst();
ConnectionDecorator cond = decorators.getSecond();
if (flow.getConditionExpression() != null && flow.getSourceRef() instanceof Activity && def == null) {
ConnectionDecorator decorator = createConditionalConnectionDecorator(connection);
GraphicsAlgorithm ga = decorator.getGraphicsAlgorithm();
ga.setFilled(true);
ga.setForeground(manageColor(connection, StyleUtil.CLASS_FOREGROUND));
ga.setBackground(manageColor(connection, IColorConstant.WHITE));
} else if (cond != null) {
peService.deletePictogramElement(cond);
}
peService.setPropertyValue(connection, IS_CONDITIONAL_FLOW_PROPERTY,
Boolean.toString(flow.getConditionExpression() != null));
}
public static class UpdateConditionalSequenceFlowFeature extends AbstractUpdateFeature {
public UpdateConditionalSequenceFlowFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canUpdate(IUpdateContext context) {
SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(context.getPictogramElement(),
SequenceFlow.class);
boolean canUpdate = flow != null && flow.getSourceRef() instanceof Activity;
return canUpdate;
}
@Override
public IReason updateNeeded(IUpdateContext context) {
// NOTE: if this method returns "true" the very first time it's called by the refresh
// framework, the connection line will be drawn as a red dotted line, so make sure
// all properties have been correctly set to their initial values in the AddFeature!
// see https://issues.jboss.org/browse/JBPM-3102
IPeService peService = Graphiti.getPeService();
Connection connection = (Connection) context.getPictogramElement();
SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(connection, SequenceFlow.class);
String property = peService.getPropertyValue(connection, IS_CONDITIONAL_FLOW_PROPERTY);
if (property == null || !canUpdate(context)) {
return Reason.createFalseReason();
}
boolean changed = flow.getConditionExpression() != null != new Boolean(property);
return changed ? Reason.createTrueReason() : Reason.createFalseReason();
}
@Override
public boolean update(IUpdateContext context) {
Connection connection = (Connection) context.getPictogramElement();
setConditionalSequenceFlow(connection);
return true;
}
}
public static class SequenceFlowReconnectionFeature extends BaseElementReconnectionFeature {
public SequenceFlowReconnectionFeature(IFeatureProvider fp) {
super(fp);
// TODO Auto-generated constructor stub
}
@Override
public boolean canReconnect(IReconnectionContext context) {
if (super.canReconnect(context)) {
BaseElement targetElement = BusinessObjectUtil.getFirstElementOfType(context.getTargetPictogramElement(), BaseElement.class);
return targetElement instanceof FlowNode;
}
return false;
}
// @Override
// public void postReconnect(IReconnectionContext context) {
// super.postReconnect(context);
// SequenceFlow flow = BusinessObjectUtil.getFirstElementOfType(context.getConnection(), SequenceFlow.class);
// FlowNode targetElement = BusinessObjectUtil.getFirstElementOfType(context.getTargetPictogramElement(), FlowNode.class);
// if (context.getReconnectType().equals(ReconnectionContext.RECONNECT_TARGET)) {
// flow.setTargetRef(targetElement);
// }
// else {
// flow.setSourceRef(targetElement);
// }
// }
}
private static boolean isDefaultAttributeSupported(FlowNode node) {
if (node instanceof Activity) {
return true;
}
if (node instanceof ExclusiveGateway || node instanceof InclusiveGateway || node instanceof ComplexGateway) {
return true;
}
return false;
}
private static SequenceFlow getDefaultFlow(FlowNode node) {
if (isDefaultAttributeSupported(node)) {
try {
return (SequenceFlow) node.getClass().getMethod("getDefault").invoke(node);
} catch (Exception e) {
Activator.logError(e);
}
}
return null;
}
private static Tuple<ConnectionDecorator, ConnectionDecorator> getConnectionDecorators(Connection connection) {
IPeService peService = Graphiti.getPeService();
ConnectionDecorator defaultDecorator = null;
ConnectionDecorator conditionalDecorator = null;
Iterator<ConnectionDecorator> iterator = connection.getConnectionDecorators().iterator();
while (iterator.hasNext()) {
ConnectionDecorator connectionDecorator = iterator.next();
String defProp = peService.getPropertyValue(connectionDecorator, DEFAULT_MARKER_PROPERTY);
if (defProp != null && new Boolean(defProp)) {
defaultDecorator = connectionDecorator;
continue;
}
String condProp = peService.getPropertyValue(connectionDecorator, CONDITIONAL_MARKER_PROPERTY);
if (condProp != null && new Boolean(condProp)) {
conditionalDecorator = connectionDecorator;
}
}
return new Tuple<ConnectionDecorator, ConnectionDecorator>(defaultDecorator, conditionalDecorator);
}
private static ConnectionDecorator createDefaultConnectionDecorator(Connection connection) {
ConnectionDecorator marker = Graphiti.getPeService().createConnectionDecorator(connection, false, 0.0, true);
Graphiti.getGaService().createPolyline(marker, new int[] { -5, 5, -10, -5 });
Graphiti.getPeService().setPropertyValue(marker, DEFAULT_MARKER_PROPERTY, Boolean.toString(true));
return marker;
}
private static ConnectionDecorator createConditionalConnectionDecorator(Connection connection) {
ConnectionDecorator marker = Graphiti.getPeService().createConnectionDecorator(connection, false, 0.0, true);
Graphiti.getGaService().createPolygon(marker, new int[] { -15, 0, -7, 5, 0, 0, -7, -5 });
Graphiti.getPeService().setPropertyValue(marker, CONDITIONAL_MARKER_PROPERTY, Boolean.toString(true));
return marker;
}
}