/*
* 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 net.sf.orcc.df.DfFactory;
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 org.eclipse.graphiti.features.context.IAddConnectionContext;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.ICreateConnectionContext;
import org.eclipse.graphiti.features.context.impl.AddConnectionContext;
import org.eclipse.graphiti.mm.GraphicsAlgorithmContainer;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polygon;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.AnchorContainer;
import org.eclipse.graphiti.mm.pictograms.ChopboxAnchor;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator;
import org.eclipse.graphiti.mm.pictograms.FixPointAnchor;
import org.eclipse.graphiti.mm.pictograms.FreeFormConnection;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.pattern.AbstractConnectionPattern;
import org.eclipse.graphiti.pattern.IFeatureProviderWithPatterns;
import org.eclipse.graphiti.pattern.IPattern;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeCreateService;
/**
* Implements a visible connection between 2 ports.
*
* Each connection links an input and an output port. A port can be represented
* by an Input or an Output port directly contained in the network, or a port in
* an Instance. In the second case, the real port is a port contained in an
* Actor or in a Network, depending on how the instance has been refined.
*
* @author Antoine Lorence
*
*/
public class ConnectionPattern extends AbstractConnectionPattern {
private static enum PortKind {
INPUT, OUTPUT
}
private static enum PortContainer {
NETWORK, INSTANCE
}
private static enum ConnectionSide {
SOURCE, TARGET
}
/**
* Define a simple utility class to encapsulate all information about a
* source or a target port. It is used to check validity of a connection,
* and to create the Connection instance to add in the Network.
*
* @author Antoine Lorence
*/
public class PortInformation {
private final Vertex vertex;
private final Port port;
private final PortKind kind;
private final PortContainer container;
public PortInformation(Vertex vertex, Port port, PortKind direction, PortContainer type) {
this.vertex = vertex;
this.port = port;
this.kind = direction;
this.container = type;
}
/**
* Return the vertex associated to the port. For an Instance port, it is
* the instance. For the current Network port, it is the port itself
*
* @return
*/
public Vertex getVertex() {
return vertex;
}
/**
* If the vertex is an instance, returns the Port of the refinement
* (actor or network) of this instance. In case of a port in the current
* Network, this method returns null.
*
* @return
*/
public Port getPort() {
return port;
}
/**
* Returns the port side (input or output)
*
* @return
*/
public PortKind getKind() {
return kind;
}
public PortContainer getContainer() {
return container;
}
public ConnectionSide getConnectionSide() {
if (container.equals(PortContainer.NETWORK)) {
return kind == PortKind.INPUT ? ConnectionSide.SOURCE : ConnectionSide.TARGET;
} else {
return kind == PortKind.INPUT ? ConnectionSide.TARGET : ConnectionSide.SOURCE;
}
}
}
@Override
public String getCreateName() {
return "Connection";
}
@Override
public String getCreateDescription() {
return "Add a connection between 2 ports";
}
@Override
public boolean canStartConnection(ICreateConnectionContext context) {
final Anchor anchor = context.getSourceAnchor();
final PortInformation src = getPortInformations(anchor);
if (src == null) {
return false;
}
if (src.getConnectionSide() == ConnectionSide.TARGET) {
if (anchor.getIncomingConnections().size() > 0) {
return false;
}
}
return true;
}
@Override
public boolean canCreate(ICreateConnectionContext context) {
// The current diagram has a Network attached
if (!(getBusinessObjectForPictogramElement(getDiagram()) instanceof Network)) {
return false;
}
final PortInformation src = getPortInformations(context.getSourceAnchor());
final PortInformation trgt = getPortInformations(context.getTargetAnchor());
if (src == null || trgt == null) {
return false;
}
// Disallow connection between 2 network ports
if (src.getContainer() == PortContainer.NETWORK && trgt.getContainer() == PortContainer.NETWORK) {
return false;
}
// Check incompatible connections source/target
if (src.getConnectionSide() == trgt.getConnectionSide()) {
return false;
}
// Check (for the target) if no connection is already targeting this
// port
if (src.getConnectionSide() == ConnectionSide.TARGET) {
if (context.getSourceAnchor().getIncomingConnections().size() > 0) {
return false;
}
} else if (trgt.getConnectionSide() == ConnectionSide.TARGET) {
if (context.getTargetAnchor().getIncomingConnections().size() > 0) {
return false;
}
} else {
OrccLogger.warnln("Oops, unknown error appear in ConnectionPattern. A connection cannot be created "
+ "between 2 sources or a bug may be fixed in the source code.");
return false;
}
return true;
}
@Override
public Connection create(ICreateConnectionContext context) {
PortInformation src = getPortInformations(context.getSourceAnchor());
final AddConnectionContext addContext;
// Create connection context. In some case, connection source and target
// need to be exchanged, to avoid arrow on the wrong side of the
// connection shape
if (src.getConnectionSide() != ConnectionSide.SOURCE) {
addContext = new AddConnectionContext(context.getTargetAnchor(), context.getSourceAnchor());
src = getPortInformations(context.getTargetAnchor());
} else {
addContext = new AddConnectionContext(context.getSourceAnchor(), context.getTargetAnchor());
}
final PortInformation tgt = getPortInformations(addContext.getTargetAnchor());
// Create new business object
final net.sf.orcc.df.Connection dfConnection = DfFactory.eINSTANCE.createConnection(src.getVertex(),
src.getPort(), tgt.getVertex(), tgt.getPort());
final Network network = (Network) getBusinessObjectForPictogramElement(getDiagram());
network.add(dfConnection);
addContext.setNewObject(dfConnection);
final Connection newConnection = (Connection) getFeatureProvider().addIfPossible(addContext);
return newConnection;
}
@Override
public boolean canAdd(IAddContext context) {
if (context instanceof IAddConnectionContext) {
if (context.getNewObject() instanceof net.sf.orcc.df.Connection) {
return true;
}
}
return super.canAdd(context);
}
@Override
public PictogramElement add(IAddContext context) {
final IAddConnectionContext addConContext = (IAddConnectionContext) context;
final net.sf.orcc.df.Connection addedConnection = (net.sf.orcc.df.Connection) context.getNewObject();
final IPeCreateService peCreateService = Graphiti.getPeCreateService();
final IGaService gaService = Graphiti.getGaService();
// Create the connection
final FreeFormConnection connection = peCreateService.createFreeFormConnection(getDiagram());
connection.setStart(addConContext.getSourceAnchor());
connection.setEnd(addConContext.getTargetAnchor());
// Create the line corresponding to the connection
final Polyline polyline = gaService.createPolyline(connection);
final ConnectionDecorator cd = peCreateService.createConnectionDecorator(connection, false, 1.0, true);
// Draw the arrow on the target side of the connection
final GraphicsAlgorithm arrow = createArrow(cd);
// Setup styles
polyline.setStyle(StyleUtil.connection(getDiagram()));
arrow.setStyle(StyleUtil.connection(getDiagram()));
// create link and wire it
link(connection, addedConnection);
return connection;
}
/**
* Create the arrow to display on the target side of the connection
*
* @param gaContainer
* @return
*/
private GraphicsAlgorithm createArrow(final GraphicsAlgorithmContainer gaContainer) {
final int w = 12, l = 5;
final int[] xy = new int[] { 0, 0, -w, l, -w, -l };
final Polygon figure = Graphiti.getGaService().createPolygon(gaContainer, xy);
figure.setLineVisible(false);
return figure;
}
/**
* Retrieve all information related to a port in a diagram. Build
* corresponding PortInformation structure.
*
* @param anchor
* The anchor corresponding to a port in a Network or an Instance
* @return
*/
public PortInformation getPortInformations(final Anchor anchor) {
if (anchor == null) {
return null;
}
if (anchor instanceof FixPointAnchor) {
// Instance port
final FixPointAnchor brAnchor = (FixPointAnchor) anchor;
final Port port = (Port) getBusinessObjectForPictogramElement(brAnchor);
final PortKind kind = PropsUtil.isInstanceInPort(brAnchor) ? PortKind.INPUT : PortKind.OUTPUT;
final Instance parentInstance = (Instance) getBusinessObjectForPictogramElement(brAnchor.getParent());
return new PortInformation(parentInstance, port, kind, PortContainer.INSTANCE);
} else if (anchor instanceof ChopboxAnchor) {
// Network port
final AnchorContainer container = anchor.getParent();
final Port port = (Port) getBusinessObjectForPictogramElement(container);
// Retrieve the pattern corresponding to the current port
final IPattern ipattern = ((IFeatureProviderWithPatterns) getFeatureProvider())
.getPatternForPictogramElement(anchor.getParent());
// Check the type of this pattern to know the kind of the port
final PortKind kind = ipattern instanceof InputNetworkPortPattern ? PortKind.INPUT
: PortKind.OUTPUT;
return new PortInformation(port, null, kind, PortContainer.NETWORK);
}
// Anchor without port ? Maybe an error here...
return null;
}
}