/*
* Copyright (c) 2013, IETR/INSA of Rennes
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the IETR/INSA of Rennes nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package net.sf.orcc.xdf.ui.patterns;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.orcc.df.Actor;
import net.sf.orcc.df.DfFactory;
import net.sf.orcc.df.Entity;
import net.sf.orcc.df.Instance;
import net.sf.orcc.df.Network;
import net.sf.orcc.df.Port;
import net.sf.orcc.graph.Vertex;
import net.sf.orcc.util.OrccLogger;
import net.sf.orcc.xdf.ui.styles.StyleUtil;
import net.sf.orcc.xdf.ui.util.PropsUtil;
import net.sf.orcc.xdf.ui.util.XdfUtil;
import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.graphiti.features.IDirectEditingInfo;
import org.eclipse.graphiti.features.IReason;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.ICreateContext;
import org.eclipse.graphiti.features.context.IDeleteContext;
import org.eclipse.graphiti.features.context.IDirectEditingContext;
import org.eclipse.graphiti.features.context.ILayoutContext;
import org.eclipse.graphiti.features.context.IMoveShapeContext;
import org.eclipse.graphiti.features.context.IResizeShapeContext;
import org.eclipse.graphiti.features.context.IUpdateContext;
import org.eclipse.graphiti.features.context.impl.ResizeShapeContext;
import org.eclipse.graphiti.features.impl.Reason;
import org.eclipse.graphiti.func.IDirectEditing;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.algorithms.Rectangle;
import org.eclipse.graphiti.mm.algorithms.RoundedRectangle;
import org.eclipse.graphiti.mm.algorithms.Text;
import org.eclipse.graphiti.mm.algorithms.styles.Orientation;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.AnchorContainer;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.FixPointAnchor;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.pattern.AbstractPattern;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeCreateService;
import org.eclipse.jface.dialogs.MessageDialog;
/**
* This class configure as most features as possible, relative to Instances that
* can be added to a Network.
*
* @author Antoine Lorence
*
*/
public class InstancePattern extends AbstractPattern {
// Minimal and default width for an instance shape
public static final int TOTAL_MIN_WIDTH = 120;
// Minimal and default height for an instance shape
private static final int TOTAL_MIN_HEIGHT = 80;
// Height of instance label (displaying instance name)
private static final int LABEL_HEIGHT = 40;
// Width of the line shape used as separator
private static final int SEPARATOR = 1;
// Minimal space between an input and output port on the same line
private static final int PORTS_AREAS_SPACE = 14;
// Width of the square representing a port
private static final int PORT_SIDE_WITH = 12;
// Space set around a port square
private static final int PORT_MARGIN = 2;
// Identifiers for important shape of an instance
private static final String LABEL_ID = "INSTANCE_LABEL";
private static final String SEP_ID = "INSTANCE_SEPARATOR";
private static final String PORT_ID = "INSTANCE_PORT";
private static final String PORT_TEXT_ID = "INSTANCE_PORT_TEXT";
private static final String PORT_NAME_KEY = "REF_PORT_NAME";
private static final String REFINEMENT_KEY = "refinement";
private enum Direction {
INPUTS, OUTPUTS
}
public InstancePattern() {
super(null);
}
@Override
public String getCreateName() {
return "Instance";
}
@Override
public String getCreateDescription() {
return "Create a new instance, to encapsulate a network or an actor";
}
@Override
public boolean isMainBusinessObjectApplicable(Object object) {
if (object instanceof Port) {
final Port port = (Port) object;
final EObject ctr = port.eContainer();
if (ctr instanceof Actor) {
return true;
} else if (ctr instanceof Network) {
// In this case, we must ensure the network containing this
// port is NOT the network linked to the diagram.
return ctr != getBusinessObjectForPictogramElement(getDiagram());
}
}
return object instanceof Instance;
}
@Override
protected boolean isPatternControlled(PictogramElement pe) {
if (isPatternRoot(pe)) {
return true;
} else if (PropsUtil.isInstancePort(pe)) {
return true;
}
return false;
}
@Override
protected boolean isPatternRoot(PictogramElement pe) {
return PropsUtil.isInstance(pe);
}
@Override
public boolean canDirectEdit(IDirectEditingContext context) {
boolean isText = context.getGraphicsAlgorithm() instanceof Text;
boolean isLabel = PropsUtil.isExpectedPc(context.getGraphicsAlgorithm(), LABEL_ID);
return isText && isLabel;
}
@Override
public int getEditingType() {
return IDirectEditing.TYPE_TEXT;
}
@Override
public String getInitialValue(IDirectEditingContext context) {
final Instance obj = (Instance) getBusinessObjectForPictogramElement(context.getPictogramElement());
return obj.getName();
}
@Override
public void setValue(String value, IDirectEditingContext context) {
final PictogramElement pe = context.getPictogramElement();
final Instance obj = (Instance) getBusinessObjectForPictogramElement(pe);
obj.setName(value);
updatePictogramElement(pe);
}
@Override
public String checkValueValid(String value, IDirectEditingContext context) {
final Instance instance = (Instance) getBusinessObjectForPictogramElement(context
.getPictogramElement());
return checkValueValid(value, instance);
}
public String checkValueValid(final String value, final Instance instance) {
if (value.length() < 1) {
return "Please enter a text to name the Instance.";
}
if (!value.matches("[a-zA-Z][a-zA-Z0-9_]*")) {
return "Instance name must start with a letter, and contains only alphanumeric characters";
}
final Network network = (Network) getBusinessObjectForPictogramElement(getDiagram());
for (final Vertex vertex : network.getVertices()) {
if (!vertex.equals(instance) && vertex.getLabel().equals(value)) {
final String vertexType = vertex instanceof Instance ? "an instance"
: "a port";
return "The network already contains a vertex of the same name ("
+ vertexType + ")";
}
}
// null -> value is valid
return null;
}
@Override
public void preDelete(IDeleteContext context) {
final PictogramElement pe = context.getPictogramElement();
if (pe instanceof AnchorContainer) {
XdfUtil.deleteConnections(getFeatureProvider(),
(AnchorContainer) pe);
}
}
@Override
public boolean canCreate(ICreateContext context) {
// We create the instance in a diagram
if (context.getTargetContainer() instanceof Diagram) {
// A network is associated to this diagram
if (getBusinessObjectForPictogramElement(context.getTargetContainer()) instanceof Network) {
return true;
}
}
return false;
}
@Override
public Object[] create(ICreateContext context) {
final Network network = (Network) getBusinessObjectForPictogramElement(getDiagram());
final Instance newInstance = DfFactory.eINSTANCE.createInstance();
newInstance.setName(XdfUtil.uniqueVertexName(network, "instance"));
// Request adding the shape to the diagram
addGraphicalRepresentation(context, newInstance);
// Activate direct editing on creation. A label input appear to allow
// user to type a name for the instance
getFeatureProvider().getDirectEditingInfo().setActive(true);
return new Object[] { newInstance };
}
@Override
public boolean canAdd(IAddContext context) {
if (context.getTargetContainer() instanceof Diagram) {
return isMainBusinessObjectApplicable(context.getNewObject());
}
return false;
}
@Override
public PictogramElement add(IAddContext context) {
final Diagram targetDiagram = (Diagram) context.getTargetContainer();
final IPeCreateService peCreateService = Graphiti.getPeCreateService();
final IGaService gaService = Graphiti.getGaService();
final Instance addedDomainObject = (Instance) context.getNewObject();
// Add the new Instance to the current Network
final Network network = (Network) getBusinessObjectForPictogramElement(getDiagram());
network.add(addedDomainObject);
// Create the container shape
final ContainerShape topLevelShape = peCreateService.createContainerShape(targetDiagram, true);
PropsUtil.setInstance(topLevelShape);
// Create the container graphic
final RoundedRectangle roundedRectangle = gaService.createPlainRoundedRectangle(topLevelShape, 5, 5);
roundedRectangle.setStyle(StyleUtil.basicInstanceShape(getDiagram()));
gaService.setLocationAndSize(roundedRectangle, context.getX(), context.getY(), TOTAL_MIN_WIDTH,
TOTAL_MIN_HEIGHT);
// The text label for Instance name
final Text text = gaService.createPlainText(roundedRectangle);
PropsUtil.setIdentifier(text, LABEL_ID);
// Set properties on instance label
text.setStyle(StyleUtil.instanceText(getDiagram()));
gaService.setLocationAndSize(text, 0, 0, TOTAL_MIN_WIDTH, LABEL_HEIGHT);
if (addedDomainObject.getName() != null) {
text.setValue(addedDomainObject.getName());
}
// The line separator
final int[] xy = { 0, LABEL_HEIGHT, TOTAL_MIN_WIDTH, LABEL_HEIGHT };
final Polyline line = gaService.createPlainPolyline(roundedRectangle, xy);
PropsUtil.setIdentifier(line, SEP_ID);
line.setLineWidth(SEPARATOR);
// Configure direct editing
// 1- Get the IDirectEditingInfo object
final IDirectEditingInfo directEditingInfo = getFeatureProvider().getDirectEditingInfo();
// 2- These 2 members will be used to retrieve the pattern to call for
// direct editing
directEditingInfo.setPictogramElement(topLevelShape);
directEditingInfo.setGraphicsAlgorithm(text);
// 3- This PictogramElement is used to locate input on the diagram
directEditingInfo.setMainPictogramElement(topLevelShape);
// We link graphical representation and domain model object
link(topLevelShape, addedDomainObject);
if (addedDomainObject.getEntity() != null) {
updateRefinement(topLevelShape, addedDomainObject.getEntity());
}
return topLevelShape;
}
@Override
public boolean canMoveShape(IMoveShapeContext context) {
return isPatternRoot(context.getPictogramElement())
&& context.getTargetContainer() == getDiagram();
}
@Override
public boolean canResizeShape(IResizeShapeContext context) {
// Resize is always Ok for Instance. New size is set to minimal value
// when needed
return isPatternRoot(context.getPictogramElement());
}
@Override
public void resizeShape(IResizeShapeContext context) {
final PictogramElement pe = context.getPictogramElement();
final int oldWidth = pe.getGraphicsAlgorithm().getWidth();
final int newWidth = Math.max(context.getWidth(), getInstanceMinWidth(pe));
pe.getGraphicsAlgorithm().setWidth(newWidth);
final int oldHeight = pe.getGraphicsAlgorithm().getHeight();
final int newHeight = Math.max(context.getHeight(), getInstanceMinHeight(pe));
pe.getGraphicsAlgorithm().setHeight(newHeight);
// Recalculate position of the shape if direction is NORTH or EAST
final int rsDir = context.getDirection();
if ((rsDir & IResizeShapeContext.DIRECTION_WEST) != 0) {
int westMoveSize = oldWidth - newWidth;
pe.getGraphicsAlgorithm().setX(pe.getGraphicsAlgorithm().getX() + westMoveSize);
}
if ((rsDir & IResizeShapeContext.DIRECTION_NORTH) != 0) {
int northMoveSize = oldHeight - newHeight;
pe.getGraphicsAlgorithm().setY(pe.getGraphicsAlgorithm().getY() + northMoveSize);
}
layoutPictogramElement(pe);
}
@Override
public boolean canLayout(ILayoutContext context) {
return isPatternRoot(context.getPictogramElement());
}
/**
* This function set position for all elements in an Instance Shape.
*/
@Override
public boolean layout(ILayoutContext context) {
final AnchorContainer instanceShape = (AnchorContainer) context.getPictogramElement();
final IGaService gaService = Graphiti.getGaService();
if (!isPatternRoot(instanceShape)) {
return false;
}
// Calculate the current size of the instance rectangle
final int instanceW = gaService.calculateSize(instanceShape.getGraphicsAlgorithm(), true).getWidth();
// Update label size and position
final Text label = (Text) PropsUtil.findPcFromIdentifier(instanceShape, LABEL_ID);
gaService.setLocationAndSize(label, 0, 0, instanceW, LABEL_HEIGHT);
// Update separator points
final Polyline sep = (Polyline) PropsUtil.findPcFromIdentifier(instanceShape, SEP_ID);
for (final Point p : sep.getPoints()) {
p.setY(LABEL_HEIGHT);
}
sep.getPoints().get(1).setX(instanceW);
// ***********************
// Update ports
// ***********************
int inIndex = 0, outIndex = 0;
for (final Anchor anchor : instanceShape.getAnchors()) {
if (PropsUtil.isInstanceInPort(anchor)) {
layoutPort((FixPointAnchor) anchor, inIndex++, instanceShape);
} else if (PropsUtil.isInstanceOutPort(anchor)) {
layoutPort((FixPointAnchor) anchor, outIndex++, instanceShape);
}
}
return true;
}
@Override
public boolean canUpdate(IUpdateContext context) {
return isPatternRoot(context.getPictogramElement());
}
@Override
public IReason updateNeeded(IUpdateContext context) {
final PictogramElement pe = context.getPictogramElement();
if (!isPatternRoot(pe)) {
return Reason
.createFalseReason("Given PE is not an Instance shape");
}
final Text text = (Text) PropsUtil.findPcFromIdentifier(
pe, LABEL_ID);
if (text == null) {
return Reason.createFalseReason("Label Not found !!");
}
final Instance instance = (Instance) getBusinessObjectForPictogramElement(pe);
if (!text.getValue().equals(instance.getName())) {
return Reason
.createTrueReason("The instance name has been updated from outside of the diagram");
}
final EObject refinement = instance.getEntity();
if (refinement == null || refinement.eIsProxy()) {
final String plateforStringUri = Graphiti.getPeService()
.getPropertyValue(pe, REFINEMENT_KEY);
if (plateforStringUri == null) {
// The instance has never been refined
return Reason.createFalseReason();
} else {
return Reason.createTrueReason("Invalid refinement");
}
}
// Compute list of in and out ports names
final List<String> inNames = new ArrayList<String>(), outNames = new ArrayList<String>();
for (final Anchor anchor : ((AnchorContainer) pe).getAnchors()) {
final String portName = Graphiti.getPeService().getPropertyValue(
anchor, PORT_NAME_KEY);
if(PropsUtil.isInstanceInPort(anchor)) {
inNames.add(portName);
} else if (PropsUtil.isInstanceOutPort(anchor)) {
outNames.add(portName);
}
}
// Initialize the reason object, used in folowing tests
final IReason portsUpdatedReason = Reason
.createTrueReason("The port order or their names have to be updated.");
final Entity entity = instance.getAdapter(Entity.class);
if (inNames.size() != entity.getInputs().size()
|| outNames.size() != entity.getOutputs().size()) {
return portsUpdatedReason;
}
for (int i = 0; i < inNames.size(); ++i) {
final String portName = entity.getInputs().get(i).getName();
if (!inNames.get(i).equals(portName)) {
return portsUpdatedReason;
}
}
for (int i = 0; i < outNames.size(); ++i) {
final String portName = entity.getOutputs().get(i).getName();
if (!outNames.get(i).equals(portName)) {
return portsUpdatedReason;
}
}
return super.updateNeeded(context);
}
@Override
public boolean update(IUpdateContext context) {
final PictogramElement pe = context.getPictogramElement();
if (PropsUtil.isInstance(pe)) {
final Text text = (Text) PropsUtil.findPcFromIdentifier(pe, LABEL_ID);
if (text == null) {
return false;
}
final Instance instance = (Instance) getBusinessObjectForPictogramElement(pe);
if (!instance.getName().equals(text.getValue())) {
text.setValue(instance.getName());
// Do not force refinement update in case of simply renaming
// instance
return true;
}
final ContainerShape instanceShape = (ContainerShape) pe;
final EObject refinement = instance.getEntity();
if (refinement == null || refinement.eIsProxy()) {
deleteRefinement(instanceShape);
return true;
}
updateRefinementAndRestoreConnections(instanceShape,
instance.getEntity(), instance.getName()
+ " has been updated:");
return true;
}
return super.update(context);
}
/**
* <p>
* Update the refinement (Instance or Network) for the instance linked to
* the given instanceShape. The input and output ports of the given entity
* are added to the shape.
* </p>
*
* <p>
* This method automatically updates sizes and layouts for the content. It
* doesn't save and restore existing connections eventually connected to the
* instance. Please use
* {@link InstancePattern#updateRefinementAndRestoreConnections(ContainerShape, EObject, String)}
* if the instance could already have connection (most cases).
* </p>
*
* <p>
* This method must not be called with 'null' as given entity. In that case,
* {@link #deleteRefinement(ContainerShape)} must be used instead
* </p>
*
* @param instanceShape
* The instance to refine
* @param refinement
* The entity to refine the instance on
* @return true if the refinement has been performed
*/
private boolean updateRefinement(final ContainerShape instanceShape,
final EObject refinement) {
Assert.isNotNull(refinement, "Given Entity must not be null");
if (!isPatternRoot(instanceShape)) {
return false;
}
if (!(refinement instanceof Actor || refinement instanceof Network)) {
return false;
}
// Set the current instance's entity
final Instance instance = (Instance) getBusinessObjectForPictogramElement(instanceShape);
instance.setEntity(refinement);
// Store the refinement URI in a property
Graphiti.getPeService().setPropertyValue(instanceShape, REFINEMENT_KEY,
refinement.eResource().getURI().toPlatformString(true));
// Clean all ports
final GraphicsAlgorithm instanceGa = instanceShape
.getGraphicsAlgorithm();
final List<GraphicsAlgorithm> gaChildren = new ArrayList<GraphicsAlgorithm>(
instanceGa.getGraphicsAlgorithmChildren());
for (final GraphicsAlgorithm gaChild : gaChildren) {
if (gaChild instanceof Text
&& PropsUtil.isExpectedPc(gaChild, PORT_TEXT_ID)) {
EcoreUtil.delete(gaChild, true);
}
}
final List<Anchor> anchors = new ArrayList<Anchor>(
instanceShape.getAnchors());
for (final Anchor anchor : anchors) {
EcoreUtil.delete(anchor, true);
}
// Add ports
if (instance.isActor()) {
addPorts(instanceShape, instance.getActor().getInputs(), Direction.INPUTS);
addPorts(instanceShape, instance.getActor().getOutputs(), Direction.OUTPUTS);
// Update instance style
instanceShape.getGraphicsAlgorithm().setStyle(StyleUtil.actorInstanceShape(getDiagram()));
} else {
addPorts(instanceShape, instance.getNetwork().getInputs(), Direction.INPUTS);
addPorts(instanceShape, instance.getNetwork().getOutputs(), Direction.OUTPUTS);
// Update instance style
instanceShape.getGraphicsAlgorithm().setStyle(StyleUtil.networkInstanceShape(getDiagram()));
}
// Resize to minimal size.
resizeShapeToMinimal(instanceShape);
return true;
}
/**
* <p>
* Update the refinement (Instance or Network) for the instance linked to
* the given instanceShape. The input and output ports of the given entity
* are added to the shape.
* </p>
*
* <p>
* This method internally call
* {@link #updateRefinement(ContainerShape, EObject)} but in addition, it
* saves existing connections from/to the instance and try to restore them
* after performing the refinement update. Restoration is done based on
* ports name for connections source/target.
* </p>
*
* <p>
* This method must not be called with 'null' as given refinement. In that
* case, {@link #deleteRefinement(ContainerShape)} must be used instead
* </p>
*
* @param instanceShape
* The instance shape to refine
* @param refinement
* The new actor/network to refine this instance on (Must not be
* null)
* @param msg
* The beginning of the message displayed to user at the end of
* the process
* @return true if the refinement has been performed
*/
public boolean updateRefinementAndRestoreConnections(
final ContainerShape instanceShape, final EObject refinement,
final String msg) {
Assert.isNotNull(refinement, "Given Entity must not be null");
final Map<String, Connection> incomingMap = new HashMap<String, Connection>();
final Map<String, Iterable<Connection>> outgoingMap = new HashMap<String, Iterable<Connection>>();
// Loop over all instance anchors (in & out ports) to save existing
// connections
for (final Anchor anchor : instanceShape.getAnchors()) {
final String portName = Graphiti.getPeService().getPropertyValue(
anchor, PORT_NAME_KEY);
if (anchor.getIncomingConnections().size() >= 1) {
// Save incoming connections
incomingMap.put(portName, anchor.getIncomingConnections()
.get(0));
} else if (anchor.getOutgoingConnections().size() >= 1) {
// Create a copy of the current outgoing list
final List<Connection> conList = new ArrayList<Connection>(
anchor.getOutgoingConnections());
// Save outgoing connections
outgoingMap.put(portName, conList);
}
}
// Really perform the refinement update
boolean result = updateRefinement(instanceShape, refinement);
if (incomingMap.size() == 0 && outgoingMap.size() == 0) {
// Nothing to do
return result;
}
final Entity entity = ((Instance) getBusinessObjectForPictogramElement(instanceShape))
.getAdapter(Entity.class);
// Restore connections start or end from port name they were
// connected to.
int cptReconnectedTo = 0, cptReconnectedFrom = 0;
for (final Anchor anchor : instanceShape.getAnchors()) {
final String portName = Graphiti.getPeService().getPropertyValue(
anchor, PORT_NAME_KEY);
if (PropsUtil.isInstanceInPort(anchor)
&& incomingMap.containsKey(portName)) {
final Connection connection = incomingMap.remove(portName);
// Update df connection
final net.sf.orcc.df.Connection dfConnection = ((net.sf.orcc.df.Connection) getBusinessObjectForPictogramElement(connection));
final Port inPort = entity.getInput(portName);
dfConnection.setTargetPort(inPort);
// Update Graphiti connection
connection.setEnd(anchor);
cptReconnectedTo++;
} else if (PropsUtil.isInstanceOutPort(anchor)
&& outgoingMap.containsKey(portName)) {
final Port outPort = entity.getOutput(portName);
for (final Connection connection : outgoingMap.remove(portName)) {
// Update df connection
final net.sf.orcc.df.Connection dfConnection = ((net.sf.orcc.df.Connection) getBusinessObjectForPictogramElement(connection));
dfConnection.setSourcePort(outPort);
// Update Graphiti connection
connection.setStart(anchor);
cptReconnectedFrom++;
}
}
}
// Delete resulting connections. This will prevent diagram from being in
// a strange state, where some connections have a null source or
// target anchor.
int cptDeletedConnections = 0;
for (final Connection connection : incomingMap.values()) {
XdfUtil.deleteConnection(getFeatureProvider(), connection);
cptDeletedConnections++;
}
for (final Iterable<Connection> connectionList : outgoingMap.values()) {
for (final Connection connection : connectionList) {
XdfUtil.deleteConnection(getFeatureProvider(), connection);
cptDeletedConnections++;
}
}
// Build a complete message to inform user about what happened exactly
final StringBuilder infoMsg = new StringBuilder();
infoMsg.append(msg).append('\n');
if (cptReconnectedTo > 0) {
infoMsg.append(cptReconnectedTo)
.append(" connection(s) reconnected to input port(s).")
.append('\n');
}
if (cptReconnectedFrom > 0) {
infoMsg.append(cptReconnectedFrom)
.append(" connection(s) reconnected from output port(s).")
.append('\n');
}
if (cptDeletedConnections > 0) {
infoMsg.append(cptDeletedConnections)
.append(" connection(s) deleted from the network.")
.append('\n');
}
// Inform the user about what happened
MessageDialog.openInformation(XdfUtil.getDefaultShell(),
"Instance update finished", infoMsg.toString());
return result;
}
/**
* Remove the refinement for the given instance shape and the corresponding
* business object. This method remove all ports from the shape and update
* its properties, style and size.
*
* @param instanceShape
* @return true if the action correctly ends
*/
public boolean deleteRefinement(final ContainerShape instanceShape) {
if (!isPatternRoot(instanceShape)) {
return false;
}
// Reset instance refinement
final Instance instance = (Instance) getBusinessObjectForPictogramElement(instanceShape);
instance.setEntity(null);
// Delete all connections from/to this instance shape
XdfUtil.deleteConnections(getFeatureProvider(), instanceShape);
// Clean all ports texts
final GraphicsAlgorithm instanceGa = instanceShape
.getGraphicsAlgorithm();
final List<GraphicsAlgorithm> gaChildren = new ArrayList<GraphicsAlgorithm>(
instanceGa.getGraphicsAlgorithmChildren());
for (final GraphicsAlgorithm gaChild : gaChildren) {
if (gaChild instanceof Text
&& PropsUtil.isExpectedPc(gaChild, PORT_TEXT_ID)) {
EcoreUtil.delete(gaChild, true);
}
}
// Clean all ports anchors
final List<Anchor> anchors = new ArrayList<Anchor>(
instanceShape.getAnchors());
for (final Anchor anchor : anchors) {
EcoreUtil.delete(anchor, true);
}
// Invalidate the refinement property
Graphiti.getPeService().removeProperty(instanceShape, REFINEMENT_KEY);
// Reset shape style to basic state
instanceShape.getGraphicsAlgorithm().setStyle(
StyleUtil.basicInstanceShape(getDiagram()));
// Resize to minimal size.
resizeShapeToMinimal(instanceShape);
return true;
}
/**
* Add the given list of ports in the instance, according to the given
* direction.
*
* This method only create objects and append them to the right parent. The
* layouting (setup of sizes and location) of each element is done in the
* layout method. layout() is called explicitly or implicitly from some
* methods.
*
* {@link #updateRefinement(ContainerShape, EObject)} calls this method and
* apply a resize just after. The layout() method is called from
* resizeShape() method.
*
* @param instanceShape
* The instance shape
* @param ports
* The list of ports
* @param direction
* The type of ports (inputs or outputs)
*/
private void addPorts(final ContainerShape instanceShape, final List<Port> ports, final Direction direction) {
final IPeCreateService peCreateService = Graphiti.getPeCreateService();
final IGaService gaService = Graphiti.getGaService();
final GraphicsAlgorithm instanceGa = instanceShape.getGraphicsAlgorithm();
int i = 0, j = 0;
for (final Port port : ports) {
// Create anchor
final FixPointAnchor fpAnchor = peCreateService.createFixPointAnchor(instanceShape);
fpAnchor.setUseAnchorLocationAsConnectionEndpoint(true);
PropsUtil.setIdentifier(fpAnchor, PORT_ID);
Graphiti.getPeService().setPropertyValue(fpAnchor, PORT_NAME_KEY, port.getName());
// Create the square inside anchor
final Rectangle square = gaService.createPlainRectangle(fpAnchor);
square.setStyle(StyleUtil.instancePortShape(getDiagram()));
// Create text as instance rectangle child
final Text txt = gaService.createPlainText(instanceGa, port.getName());
txt.setStyle(StyleUtil.instancePortText(getDiagram()));
PropsUtil.setIdentifier(txt, PORT_TEXT_ID);
// Setup the linking with business object
link(fpAnchor, port);
// Configure direction of the port and alignment of texts
if (direction == Direction.INPUTS) {
PropsUtil.setInstanceInPort(fpAnchor);
PropsUtil.setInstanceInPort(txt);
txt.setHorizontalAlignment(Orientation.ALIGNMENT_LEFT);
layoutPort(fpAnchor, i++, instanceShape);
} else {
PropsUtil.setInstanceOutPort(fpAnchor);
PropsUtil.setInstanceOutPort(txt);
txt.setHorizontalAlignment(Orientation.ALIGNMENT_RIGHT);
layoutPort(fpAnchor, j++, instanceShape);
}
}
}
private void layoutPort(final FixPointAnchor anchor, final int index, final PictogramElement instancePe) {
final IGaService gaService = Graphiti.getGaService();
// The port square, visual representation of the anchor
final GraphicsAlgorithm square = anchor.getGraphicsAlgorithm();
// referenced port text
final Text txt = getTextFromAnchor(anchor);
// Calculate the current size of the instance rectangle
final int instanceW = instancePe.getGraphicsAlgorithm().getWidth();
final int yScaleFromTop = LABEL_HEIGHT + SEPARATOR + PORT_MARGIN;
final int squareAndMargin = PORT_SIDE_WITH + PORT_MARGIN;
final int minTxtH = XdfUtil.getTextMinHeight(txt);
final int txtH, anchorScale;
// Height for text can change if style is updated. We need to
// recalculate correct position everytime
if (minTxtH > PORT_SIDE_WITH) {
txtH = minTxtH;
anchorScale = (minTxtH - PORT_SIDE_WITH) / 2 - minTxtH % 2;
} else {
txtH = PORT_SIDE_WITH;
anchorScale = 0;
}
final int txtW = instanceW - squareAndMargin * 2;
final int txtX = squareAndMargin;
final int txtY = yScaleFromTop + index * (txtH + PORT_MARGIN);
final int anchorY = txtY + anchorScale + PORT_SIDE_WITH / 2;
final int squareY = -PORT_SIDE_WITH / 2;
int anchorX, squareX, squareW = PORT_SIDE_WITH;
if (PropsUtil.isInstanceInPort(anchor)) {
anchorX = 0;
squareX = 0;
} else if (PropsUtil.isInstanceOutPort(anchor)) {
anchorX = instanceW;
squareX = -PORT_SIDE_WITH;
// Fix the size of outputs port, to take care of the instance border
// size. We can't change the X coordinate of the anchor or the
// square, or the orthogonal layout produce false path for some
// connections.
squareW += gaService.getLineWidth(instancePe.getGraphicsAlgorithm(), true);
} else {
OrccLogger.warnln("Anchor without \"direction\" property found.");
return;
}
// Text position is relative to its parent, the instance
// roundedRectangle (classical positioning)
gaService.setLocationAndSize(txt, txtX, txtY, txtW, txtH);
// FixPointAnchor references the txt object. Its location is
// relative to the text location
anchor.setLocation(gaService.createPoint(anchorX, anchorY));
// The square is the GA of the Anchor. Its position is calculated
// from the anchor's position
gaService.setLocationAndSize(square, squareX, squareY, squareW, PORT_SIDE_WITH);
}
/**
* Resize the current instance shape to its minimal width and height. The
* layout() method will be called after, directly from the resize feature.
*
* @param pe
* The instance pictogram element
*/
private void resizeShapeToMinimal(final PictogramElement pe) {
if (!PropsUtil.isInstance(pe)) {
return;
}
final ResizeShapeContext ctxt = new ResizeShapeContext((Shape) pe);
ctxt.setWidth(getInstanceMinWidth(pe));
ctxt.setHeight(getInstanceMinHeight(pe));
resizeShape(ctxt);
}
/**
* Calculate the minimal height needed to display the longest port list
* (between inputs and outputs ones)
*
* @param pe
* The instance pictogram element
* @return The height as integer
*/
private int getInstanceMinHeight(final PictogramElement pe) {
if (!PropsUtil.isInstance(pe)) {
return -1;
}
final ContainerShape instanceShape = (ContainerShape) pe;
int nbInPorts = 0, nbOutPorts = 0;
// Compute the number of inputs and outputs ports
for (final Anchor anchor : instanceShape.getAnchors()) {
if (PropsUtil.isInstanceInPort(anchor)) {
++nbInPorts;
} else if (PropsUtil.isInstanceOutPort(anchor)) {
++nbOutPorts;
}
}
// Keep only the max
final int nbMaxPorts = Math.max(nbInPorts, nbOutPorts);
if (nbMaxPorts == 0) {
return TOTAL_MIN_HEIGHT;
}
// Calculate the total minimal height needed to display the longest
// ports list
int portLineHeight = 0;
for (final GraphicsAlgorithm child : instanceShape.getGraphicsAlgorithm().getGraphicsAlgorithmChildren()) {
if (child instanceof Text
&& (PropsUtil.isInstanceInPort(child) || PropsUtil.isInstanceOutPort(child))) {
portLineHeight = Math.max(XdfUtil.getTextMinHeight((Text) child), PORT_SIDE_WITH);
break;
}
}
if (portLineHeight != 0) {
final int totalMinHeight = LABEL_HEIGHT + SEPARATOR + PORT_MARGIN + nbMaxPorts
* (portLineHeight + PORT_MARGIN) + PORT_MARGIN;
// Return this max height only if it is superior the the generic
// minimal height for an instance
return Math.max(totalMinHeight, TOTAL_MIN_HEIGHT);
}
// Should never happen
return TOTAL_MIN_HEIGHT;
}
/**
* Calculate the minimal width needed to display all contents (ports) of an
* instance shape.
*
* @param pe
* The instance pictogram element
* @return The width as integer
*/
private int getInstanceMinWidth(final PictogramElement pe) {
if (!PropsUtil.isInstance(pe)) {
return -1;
}
final ContainerShape instanceShape = (ContainerShape) pe;
final int minWidthWithoutTexts = PORTS_AREAS_SPACE + (PORT_SIDE_WITH + PORT_MARGIN) * 2;
final List<Text> inputs = new ArrayList<Text>();
final List<Text> outputs = new ArrayList<Text>();
// Collect the ports text instances in 2 separate maps
for (final GraphicsAlgorithm child : instanceShape.getGraphicsAlgorithm().getGraphicsAlgorithmChildren()) {
if (child instanceof Text && PropsUtil.isInstanceInPort(child)) {
inputs.add((Text) child);
} else if (child instanceof Text && PropsUtil.isInstanceOutPort(child)) {
outputs.add((Text) child);
}
}
int maxTotalWidth = TOTAL_MIN_WIDTH;
final int nbCommonports = Math.min(inputs.size(), outputs.size());
// Compute the longest space needed to display on the same line both
// input and output ports
for (int i = 0; i < nbCommonports; ++i) {
final int currentWidth = XdfUtil.getTextMinWidth(inputs.get(i)) + XdfUtil.getTextMinWidth(outputs.get(i))
+ minWidthWithoutTexts;
maxTotalWidth = Math.max(maxTotalWidth, currentWidth);
}
// Do the same for lasts in/outputs ports
if (inputs.size() > outputs.size()) {
for (int i = nbCommonports; i < inputs.size(); ++i) {
final int currentWidth = XdfUtil.getTextMinWidth(inputs.get(i)) + minWidthWithoutTexts;
maxTotalWidth = Math.max(maxTotalWidth, currentWidth);
}
} else if (inputs.size() < outputs.size()) {
for (int i = nbCommonports; i < outputs.size(); ++i) {
final int currentWidth = XdfUtil.getTextMinWidth(outputs.get(i)) + minWidthWithoutTexts;
maxTotalWidth = Math.max(maxTotalWidth, currentWidth);
}
}
return maxTotalWidth;
}
/**
* Return the name of the instance without using the business object
*
* @param pe
* @return
*/
public String getNameFromShape(final PictogramElement pe) {
if (isPatternRoot(pe)) {
final Text text = (Text) PropsUtil.findPcFromIdentifier(pe, LABEL_ID);
return text.getValue();
}
return "";
}
/**
* Returns the FixPointAnchor associated with the given port in the instance
* represented by the given PictogramElement.
*
* @param instancePe
* @param port
* @return
*/
public Anchor getAnchorForPort(final PictogramElement instancePe, final Port port) {
if (isPatternRoot(instancePe)) {
final ContainerShape instanceShape = (ContainerShape) instancePe;
for (final Anchor anchor : instanceShape.getAnchors()) {
if (getBusinessObjectForPictogramElement(anchor).equals(port)) {
return anchor;
}
}
}
return null;
}
/**
* Returns the Text object used to display the port name. The object is
* searched from the given anchor, which must represents an instance port.
*
* @param anchor
* @return
*/
public Text getTextFromAnchor(final Anchor anchor) {
final String portName = Graphiti.getPeService().getPropertyValue(anchor, PORT_NAME_KEY);
for (final GraphicsAlgorithm gaChild : anchor.getParent().getGraphicsAlgorithm().getGraphicsAlgorithmChildren()) {
if (gaChild instanceof Text && PropsUtil.isExpectedPc(gaChild, PORT_TEXT_ID)
&& ((Text) gaChild).getValue().equals(portName)) {
return (Text) gaChild;
}
}
return null;
}
}