| /***************************************************************************** |
| * Copyright (c) 2013 CEA LIST. |
| * |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Ansgar Radermacher ansgar.radermacher@cea.fr |
| * |
| *****************************************************************************/ |
| |
| package org.eclipse.papyrus.designer.components.modellibs.core.transformations; |
| |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.papyrus.designer.components.FCM.InteractionComponent; |
| import org.eclipse.papyrus.designer.components.modellibs.core.Activator; |
| import org.eclipse.papyrus.designer.components.modellibs.core.Messages; |
| import org.eclipse.papyrus.designer.components.modellibs.core.utils.CompDepUtils; |
| import org.eclipse.papyrus.designer.components.transformation.PortUtils; |
| import org.eclipse.papyrus.designer.components.transformation.sync.CompImplSync; |
| import org.eclipse.papyrus.designer.components.transformation.templates.ComponentTemplateUtils; |
| import org.eclipse.papyrus.designer.components.transformation.templates.ConnectorBinding; |
| import org.eclipse.papyrus.designer.deployment.tools.AllocUtils; |
| import org.eclipse.papyrus.designer.deployment.tools.DepCreation; |
| import org.eclipse.papyrus.designer.deployment.tools.DepUtils; |
| import org.eclipse.papyrus.designer.languages.common.base.ElementUtils; |
| import org.eclipse.papyrus.designer.transformation.base.utils.CopyUtils; |
| import org.eclipse.papyrus.designer.transformation.base.utils.TransformationException; |
| import org.eclipse.papyrus.designer.transformation.core.m2minterfaces.IM2MTrafoCDP; |
| import org.eclipse.papyrus.designer.transformation.core.templates.TemplateInstantiation; |
| import org.eclipse.papyrus.designer.transformation.core.transformations.LazyCopier; |
| import org.eclipse.papyrus.designer.transformation.core.transformations.TransformationContext; |
| import org.eclipse.papyrus.designer.transformation.profile.Transformation.M2MTrafo; |
| import org.eclipse.papyrus.uml.tools.utils.ConnectorUtil; |
| import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil; |
| import org.eclipse.uml2.uml.Class; |
| import org.eclipse.uml2.uml.Classifier; |
| import org.eclipse.uml2.uml.ConnectableElement; |
| import org.eclipse.uml2.uml.Connector; |
| import org.eclipse.uml2.uml.ConnectorEnd; |
| import org.eclipse.uml2.uml.EncapsulatedClassifier; |
| import org.eclipse.uml2.uml.InstanceSpecification; |
| import org.eclipse.uml2.uml.Package; |
| import org.eclipse.uml2.uml.Port; |
| import org.eclipse.uml2.uml.Property; |
| import org.eclipse.uml2.uml.Slot; |
| import org.eclipse.uml2.uml.TemplateBinding; |
| import org.eclipse.uml2.uml.Type; |
| import org.eclipse.uml2.uml.UMLPackage; |
| import org.eclipse.uml2.uml.util.UMLUtil; |
| |
| /** |
| * This class enables the reification of connectors, i.e. the replacement of |
| * a UML connector with an interaction component. Reification is primarily |
| * done on the level of a composite class. |
| */ |
| public class ConnectorReification implements IM2MTrafoCDP { |
| |
| /** |
| * Find a port that would match a connection |
| * |
| * @param connectorType |
| * a connector type, i.e. a component with ports |
| * @param otherPort |
| * the port on the other side of the connection |
| * @return the first port (of all ports owned or inherited by the type) that |
| * is compatible with the passed otherPort. |
| */ |
| public static Port getConnectorPort(EncapsulatedClassifier connectorType, Port otherPort, boolean isAssembly) { |
| EList<Port> ports = PortUtils.getAllPorts(connectorType); |
| // try to find matching port |
| for (Port port : ports) { |
| if (PortUtils.isCompatible(port, otherPort, isAssembly)) { |
| return port; |
| } |
| } |
| throw new RuntimeException(String.format(Messages.ConnectorReification_CannotFindMatchingPort, connectorType.getName(), otherPort.getQualifiedName())); |
| } |
| |
| /** |
| * Simple helper function that finds the opposite connector end when one is given. |
| * If returns the first connector end that is not equal to the passed one, but results |
| * will not be useful in general for n-ary connectors (n!=2) |
| * |
| * @param connector |
| * a connector |
| * @param myEnd |
| * one end of a connector. |
| * @return the connector end that is associated with the "other" end of a |
| * connection, i.e. the end that differs from the passed connector end |
| */ |
| public static ConnectorEnd oppositeConnEnd(Connector connector, ConnectorEnd myEnd) { |
| // look for the other end (connectedEnd != myEnd) |
| for (ConnectorEnd end : connector.getEnds()) { |
| if (end != myEnd) { |
| return end; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Reify a connector already represented in form of a part (useful for n-ary connectors) |
| * |
| * @param composite |
| * containing composite in target target model |
| * @param connectorPart |
| * Part representing the connector |
| * @param compositeIS |
| * target instance specification of the composite passed as 2nd parameter |
| * (required for obtaining node allocation and choosing the right implementation. |
| * Main use: decide whether a distributed implementation of an interaction component needs to be used) |
| * @return The created part within tmComponent that represents the reified |
| * connector |
| * @throws TransformationException |
| */ |
| public Property reifyConnector(Class composite, Property connectorPart, InstanceSpecification compositeIS) throws TransformationException { |
| |
| if (!(connectorPart.getType() instanceof Class)) { |
| // type is not a class, now check whether it is an interaction component |
| return null; |
| } |
| Class connImplementation = (Class) connectorPart.getType(); |
| if (!StereotypeUtil.isApplied(connImplementation, InteractionComponent.class)) { |
| return null; |
| } |
| |
| // choose an implementation |
| Class connectorImplemTemplate = CompDepUtils.chooseImplementation(connImplementation, AllocUtils.getAllNodes(compositeIS), null); |
| |
| if (connectorImplemTemplate == null) { |
| if (AllocUtils.getAllNodes(compositeIS).size() > 1) { |
| throw new TransformationException(String.format(org.eclipse.papyrus.designer.components.modellibs.core.Messages.ConnectorReification_CANT_FIND_IMPLEMENTATION_DIST, connImplementation.getName())); |
| } else { |
| throw new TransformationException(String.format(org.eclipse.papyrus.designer.components.modellibs.core.Messages.ConnectorReification_CANT_FIND_IMPLEMENTATION, connImplementation.getName())); |
| } |
| } |
| TemplateBinding binding = ConnectorBinding.obtainBinding(composite, connectorPart, connectorImplemTemplate, true); |
| Class connectorImplem; |
| |
| if (binding != null) { |
| // TemplateUtils.adaptActualsToTargetModel(copier, binding); |
| TemplateInstantiation ti = new TemplateInstantiation(binding); |
| connectorImplem = ti.bindElement(connectorImplemTemplate); |
| } else { |
| // no binding, class is not a template => copy as it is |
| connectorImplem = connectorImplemTemplate; |
| } |
| connectorPart.setType(connectorImplem); |
| // now re-target connectors towards this part |
| ComponentTemplateUtils.retargetConnectors(composite, connectorPart); |
| return connectorPart; |
| } |
| |
| /** |
| * Reify a connector |
| * |
| * @param composite |
| * containing composite |
| * @param connector |
| * connector within the passed composite. If connector reification is successful, the passed connector will be destroyed |
| * (since replaced by a new connector-part-connector combination) |
| * @param compositeIS |
| * instance specification of the composite passed as 2nd parameter |
| * (required for obtaining node allocation and choosing the right implementation. |
| * Main use: decide whether a distributed implementation of an interaction component needs to be used) |
| * @return the created part within the composite or null, if no reification could be executed. |
| * @throws TransformationException |
| */ |
| public Property reifyConnector(Class composite, Connector connector, InstanceSpecification compositeIS) throws TransformationException { |
| |
| InteractionComponent connType = null; |
| org.eclipse.papyrus.designer.components.FCM.Connector fcmConn = UMLUtil.getStereotypeApplication(connector, org.eclipse.papyrus.designer.components.FCM.Connector.class); |
| if (fcmConn != null) { |
| connType = fcmConn.getIc(); |
| } |
| if (connType == null) { |
| return null; |
| } |
| |
| LazyCopier copier = TransformationContext.current.copier; |
| String name = ElementUtils.varName(connector); |
| |
| // choose an implementation |
| Class connectorImplemTemplate = CompDepUtils.chooseImplementation(connType.getBase_Class(), AllocUtils.getAllNodes(compositeIS), null); |
| |
| // ---- obtain binding & instantiate template type ... |
| TemplateBinding binding = ConnectorBinding.obtainBinding(composite, connector, connectorImplemTemplate, true); |
| Class connectorImplem; |
| |
| if (binding != null) { |
| TemplateInstantiation ti = new TemplateInstantiation(binding); |
| connectorImplem = ti.bindElement(connectorImplemTemplate); |
| } else { |
| // no binding, class is not a template => copy as it is |
| connectorImplem = copier.getCopy(connectorImplemTemplate); |
| } |
| |
| if (connectorImplem == null) { |
| throw new TransformationException(String.format(Messages.ConnectorReification_CouldNotBind, connectorImplemTemplate.getName())); |
| } |
| |
| Property tmConnectorPart = composite.createOwnedAttribute(name, connectorImplemTemplate); |
| // copy id, but prefix it with "p" (for part) |
| CopyUtils.copyID(connector, tmConnectorPart, "p"); //$NON-NLS-1$ |
| tmConnectorPart.setIsComposite(true); |
| |
| Activator.log.info(String.format(Messages.ConnectorReification_InfoAddConnectorPart, connectorImplemTemplate.getName(), connectorImplem.getName())); |
| |
| // now create (simple) connections towards the new part |
| int i = 0; |
| for (ConnectorEnd smEnd : connector.getEnds()) { |
| Connector tmConnector = composite.createOwnedConnector("c " //$NON-NLS-1$ |
| + name + " " + String.valueOf(i)); //$NON-NLS-1$ |
| CopyUtils.copyID(connector, tmConnector); |
| i++; |
| // the new connector connects the existing end with an end of the |
| // reified connector (the newly created property.) |
| |
| // --- first end, connected with the existing end (of another non-connector part) |
| ConnectorEnd tmEnd1 = tmConnector.createEnd(); |
| Property smPartWithPort = smEnd.getPartWithPort(); |
| ConnectableElement smRole = smEnd.getRole(); |
| tmEnd1.setPartWithPort(smPartWithPort); |
| tmEnd1.setRole(smRole); |
| |
| // --- 2nd end, connected with the reified connector (new part) |
| ConnectorEnd tmEnd2 = tmConnector.createEnd(); |
| tmEnd2.setPartWithPort(tmConnectorPart); |
| // inheritance between connector type and implementation (ports should be identical) |
| // TODO: check whether filter condition is unique? (first returned by getConnectorPort is "good" one) |
| if (smRole instanceof Port) { |
| tmEnd2.setRole(getConnectorPort(connectorImplem, (Port) smRole, (smPartWithPort != null))); |
| } else { |
| throw new TransformationException(Messages.ConnectorReification_RequiresUseOfPorts); |
| } |
| } |
| |
| tmConnectorPart.setType(connectorImplem); |
| // updatePorts(tmComponent, connectorPart, connectorImplem); |
| // connectContainerPorts(tmComponent, connectorPart); |
| connector.destroy(); |
| |
| return tmConnectorPart; |
| } |
| |
| /** |
| * Components can contain additional ports that are inherited via the |
| * container extension. These ports should typically be connected with |
| * additional ports of the (reified) connector. This connection is based on |
| * equal port types and is done automatically by this function, i.e. it |
| * cannot be done by the developer. |
| * |
| * @param composite |
| * the composite in which a connector has been reified. |
| * @param reifiedConnector |
| * the part associated with the reifiedConnector |
| */ |
| public void connectContainerPorts(Class composite, Property reifiedConnector) { |
| // This function is based on the assumption that the additional ports of the reified |
| // connector need to be connected with a port of a component that is already |
| // connected via the "normal" connectors (explicitly modeled by the user). |
| // For instance, in case of ACCORD-calls, the server component provides the additional |
| // RTU port via its container. |
| |
| // Create a subset of connectors that are connected with the reified connector |
| EList<Connector> connSubset = new BasicEList<Connector>(); |
| for (Connector connector : composite.getOwnedConnectors()) { |
| if (ConnectorUtil.connectsPart(connector, reifiedConnector)) { |
| connSubset.add(connector); |
| } |
| } |
| |
| for (Port port : PortUtils.getAllPorts((Class) reifiedConnector.getType())) { |
| // check, if port is unconnected. |
| boolean connected = false; |
| // check whether a port of the reified connector is not yet |
| // connected. |
| for (Connector connector : connSubset) { |
| if (ConnectorUtil.connectsPort(connector, port)) { |
| connected = true; |
| } |
| } |
| if (!connected) { |
| // port is not connected yet. Check to find a connectable port |
| // among all ports of connected parts. |
| // In the moment, we assume the processes is stopped, as soon as |
| // the port is connected, i.e. we do not want to connect the port to |
| // potentially set of ports (todo: restriction always useful?) |
| for (Connector connector : connSubset) { |
| ConnectorEnd connEnd = ConnectorUtil.connEndNotPart(connector, reifiedConnector); |
| Property otherPart = connEnd.getPartWithPort(); |
| // this is a part which is connected with the reified |
| // connector check whether one of its ports is compatible with the |
| // non-connected port. |
| if (!(otherPart.getType() instanceof EncapsulatedClassifier)) { |
| continue; |
| } |
| |
| for (Port otherPort : PortUtils.getAllPorts((EncapsulatedClassifier) otherPart.getType())) { |
| Activator.log.info(String.format(Messages.ConnectorReification_InfoPortTypes, otherPort.getType().getQualifiedName(), port.getType().getQualifiedName())); |
| if (otherPort.getType() == port.getType()) { |
| Connector newConnector = composite.createOwnedConnector("connector - container of " //$NON-NLS-1$ |
| + otherPart.getName()); |
| ConnectorEnd end1 = newConnector.createEnd(); |
| ConnectorEnd end2 = newConnector.createEnd(); |
| end1.setPartWithPort(reifiedConnector); |
| end1.setRole(port); |
| end2.setPartWithPort(otherPart); |
| end2.setRole(otherPort); |
| connected = true; |
| break; |
| } |
| } |
| if (connected) { |
| break; |
| } |
| } |
| if (!connected) { |
| if (port.getType() == null) { |
| Activator.log.debug(String.format(Messages.ConnectorReification_CouldNotConnectPort, port.getName())); |
| } else { |
| Activator.log.debug(String.format(Messages.ConnectorReification_CouldNotConnectPortOfType, port.getName(), port.getType().getName())); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * update the implementation of an reified connector. This consists of two aspects |
| * (1) update ports |
| * (2) update the interface realizations |
| * |
| * @param is |
| * an instance specification |
| * @param implementation |
| * the new implementation |
| */ |
| protected void updateImplementation(InstanceSpecification is, Class implementation) { |
| CompImplSync.updatePorts(implementation); |
| CompImplSync.syncRealizations(implementation); |
| } |
| |
| /** |
| * Update an instance specification with a new implementation |
| * It also handles nested instance specifications |
| * |
| * @param is |
| * an instance specification |
| * @param implementation |
| * the new implementation |
| */ |
| protected void updateDepPlan(InstanceSpecification is, Class implementation) { |
| if (is != null) { |
| is.getClassifiers().set(0, implementation); |
| } |
| for (Slot slot : is.getSlots()) { |
| String name = slot.getDefiningFeature().getName(); |
| Property subAttr = implementation.getOwnedAttribute(name, null); |
| slot.setDefiningFeature(subAttr); |
| InstanceSpecification subIS = DepUtils.getInstance(slot); |
| if (subIS != null && subAttr.getType() instanceof Class) { |
| updateDepPlan(subIS, (Class) subAttr.getType()); |
| } |
| } |
| } |
| |
| @Override |
| public void applyTrafo(M2MTrafo trafo, Package deploymentPlan) throws TransformationException { |
| for (InstanceSpecification is : DepUtils.getInstances(deploymentPlan)) { |
| Classifier cl = DepUtils.getClassifier(is); |
| if (cl instanceof Class) { |
| Class clazz = (Class) cl; |
| EList<Connector> connectorListCopy = new BasicEList<Connector>(); |
| connectorListCopy.addAll(clazz.getOwnedConnectors()); |
| |
| EList<Property> attributeListCopy = new BasicEList<Property>(); |
| attributeListCopy.addAll(clazz.getAttributes()); |
| |
| for (Connector connector : connectorListCopy) { |
| Class tmComponent = (Class) connector.getOwner(); |
| Property newPart = reifyConnector(tmComponent, connector, is); |
| if (newPart != null) { |
| Type implCandidate = newPart.getType(); |
| if (implCandidate instanceof Class) { |
| // find IS via its name, as it is not referenced via a slot from |
| // the parent (the connector can not be a defining feature) |
| InstanceSpecification partIS = (InstanceSpecification) is.getNearestPackage().getMember(is.getName() + "." + connector.getName(), false, //$NON-NLS-1$ |
| UMLPackage.eINSTANCE.getInstanceSpecification()); |
| if (partIS != null) { |
| updateImplementation(partIS, (Class) implCandidate); |
| // parent instance had no slot before (as connectors can not be defining |
| // features), create it now |
| DepCreation.createSlot(is, partIS, newPart); |
| updateDepPlan(partIS, (Class) newPart.getType()); |
| } else { |
| Activator.log.debug("Can not update instance specification after connector reification"); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| |
| for (Property attribute : attributeListCopy) { |
| |
| Class tmComponent = (Class) attribute.getOwner(); |
| Property updatedPart = reifyConnector(tmComponent, attribute, is); |
| |
| if (updatedPart != null) { |
| Type implCandidate = updatedPart.getType(); |
| Slot partSlot = DepUtils.getSlot(is, updatedPart); |
| if (implCandidate instanceof Class && partSlot != null) { |
| InstanceSpecification partIS = DepUtils.getInstance(partSlot); |
| updateImplementation(partIS, (Class) implCandidate); |
| updateDepPlan(partIS, (Class) implCandidate); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |