| /***************************************************************************** |
| * Copyright (c) 2015 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.transformation.cpp.xtend |
| |
| import java.util.HashMap |
| import java.util.Map |
| import org.eclipse.emf.common.util.EList |
| import org.eclipse.papyrus.designer.components.modellibs.core.transformations.Constants |
| import org.eclipse.papyrus.designer.components.transformation.PortInfo |
| import org.eclipse.papyrus.designer.components.transformation.PortUtils |
| import org.eclipse.papyrus.designer.components.transformation.component.PrefixConstants |
| import org.eclipse.papyrus.designer.components.transformation.component.PrefixConstants.CIFvariant |
| import org.eclipse.papyrus.designer.components.transformation.cpp.Messages |
| import org.eclipse.papyrus.designer.components.transformation.extensions.IOOTrafo |
| import org.eclipse.papyrus.designer.languages.common.base.ElementUtils |
| import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Ptr |
| import org.eclipse.papyrus.designer.transformation.base.utils.CopyUtils |
| import org.eclipse.papyrus.designer.transformation.base.utils.PartsUtil |
| import org.eclipse.papyrus.designer.transformation.base.utils.TransformationException |
| import org.eclipse.papyrus.designer.transformation.core.transformations.LazyCopier |
| import org.eclipse.papyrus.uml.tools.utils.ConnectorUtil |
| import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil |
| import org.eclipse.uml2.uml.AggregationKind |
| import org.eclipse.uml2.uml.Class |
| import org.eclipse.uml2.uml.Connector |
| import org.eclipse.uml2.uml.ConnectorEnd |
| import org.eclipse.uml2.uml.OpaqueBehavior |
| import org.eclipse.uml2.uml.Port |
| import org.eclipse.uml2.uml.Property |
| import org.eclipse.uml2.uml.Type |
| import org.eclipse.uml2.uml.UMLPackage |
| |
| import static extension org.eclipse.papyrus.designer.components.transformation.cpp.xtend.CppUtils.nameRef |
| |
| /** |
| * This class realizes the dynamic variant of the OO-transformation |
| * |
| * TODO: currently not tested/used, needs to be aligned with abstract transformation |
| * In does currently not support multiple references, as the static variant |
| */ |
| class DynamicCppToOO implements IOOTrafo { |
| |
| protected LazyCopier copier |
| |
| val static String PART_MANAGER = "services::PartManager" |
| |
| val static String INIT_PARTS = "initParts" |
| |
| // name of generic "parts" attribute for dynamic deployment (TODO: should be inherited) |
| val static String PARTS = "parts" |
| |
| val static String progLang = "C/C++" |
| |
| protected Class bootloader // why required? |
| |
| override init(LazyCopier copier, Class bootloader) { |
| PrefixConstants.init(CIFvariant.UML); |
| this.copier = copier |
| this.bootloader = bootloader |
| } |
| |
| override addPortOperations(Class implementation) { |
| addGetPortOperation(implementation) |
| addConnectPortOperation(implementation) |
| } |
| |
| /** |
| * Add the get_p operation for each port with a provided interface. It also |
| * adds a suitable implementation that evaluates delegation connectors from |
| * the port to a property within the composite. The delegation target could |
| * either be a normal class (no port) or an inner component. |
| * |
| * @param implementation |
| */ |
| def addGetPortOperation(Class implementation) { |
| for (PortInfo portInfo : PortUtils.flattenExtendedPorts(PortUtils.getAllPorts2(implementation))) { |
| val providedIntf = portInfo.getProvided() |
| if (providedIntf !== null) { |
| |
| // port provides an interface, add "get_p" operation & |
| // implementation |
| val opName = PrefixConstants.getP_Prefix + portInfo.name |
| var op = implementation.getOwnedOperation(opName, null, null) |
| if (op !== null) { |
| |
| // operation already exists. Assume that user wants to |
| // override standard delegation |
| if (op.type != providedIntf) { |
| op.createOwnedParameter(Constants.retParamName, providedIntf) |
| } |
| } else { |
| op = implementation.createOwnedOperation(opName, null, null, providedIntf) |
| val retParam = op.getOwnedParameters().get(0) |
| retParam.setName(Constants.retParamName) |
| StereotypeUtil.apply(retParam, Ptr) |
| |
| val behavior = implementation.createOwnedBehavior(opName, UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior |
| op.getMethods().add(behavior) |
| |
| val ce = ConnectorUtil.getDelegation(implementation, portInfo.getModelPort()) |
| |
| // if there is an delegation to an inner property, delegate to |
| // it |
| // Make distinction between delegation to component (with a |
| // port) or |
| // "normal" class (without). |
| var String body |
| if (ce !== null) { |
| val part = ce.partWithPort |
| val role = ce.role |
| |
| body = "return " |
| if (role instanceof Port) { |
| |
| // check whether the part exists within the implementation (might not be the case |
| // due to partially copied composites). |
| // Check is based on names, since the connector points to elements within another |
| // model (copyClassifier does not make a proper connector copy) |
| // TODO: this will NOT work for extended ports! |
| body += '''«part.nameRef»«PrefixConstants.getP_Prefix»«role.name»();''' |
| } else { |
| |
| // role is not a port: connector connects directly to a |
| // structural feature |
| // without passing via a port |
| // TODO: check whether structural feature exists |
| body += role.name |
| } |
| } else { |
| |
| // no delegation, check whether port implements provided interface |
| var implementsIntf = implementation.getInterfaceRealization(null, providedIntf) !== null |
| if (!implementsIntf) { |
| |
| // The extended port itself is not copied to the target |
| // model (since referenced via a stereotype). Therefore, |
| // a port of an extended port still points to the |
| // original model. We try whether the providedIntf |
| // within |
| // the target model is within the interface |
| // realizations. |
| val providedIntfInCopy = copier.getCopy(providedIntf) |
| implementsIntf = implementation.getInterfaceRealization(null, providedIntfInCopy) !== null |
| } |
| if (implementsIntf) { |
| body = "return this;" |
| } else { |
| throw new RuntimeException( |
| String.format(Messages.CompImplTrafos_IntfNotImplemented, providedIntf.name, |
| portInfo.port.name, implementation.name)) |
| } |
| } |
| behavior.getLanguages().add(progLang) |
| behavior.getBodies().add(body) |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add a connect_<portName> operation for ports with a required interface. |
| * Whereas operation and a behavior is added for each owned port, a behavior |
| * (method) is needed for ports inherited from a component type (the |
| * behavior is implementation specific, as it needs to take delegation to |
| * parts into account) |
| * |
| * @param implementation |
| */ |
| static def addConnectPortOperation(Class implementation) { |
| for (PortInfo portInfo : PortUtils.flattenExtendedPorts(PortUtils.getAllPorts2(implementation))) { |
| val requiredIntf = portInfo.getRequired() |
| if (requiredIntf !== null) { |
| |
| // port requires an interface, add "connect_p" operation & |
| // implementation |
| val opName = PrefixConstants.connectQ_Prefix + portInfo.name |
| |
| if (implementation.getOwnedOperation(opName, null, null) !== null) { |
| // do not add the operation, if it already exists. This means that the |
| // user wants to override it with custom behavior. In case of extended |
| // ports, we may have to do that. |
| } else { |
| var op = implementation.createOwnedOperation(opName, null, null) |
| |
| val refParam = op.createOwnedParameter("ref", requiredIntf) |
| StereotypeUtil.apply(refParam, Ptr) |
| |
| val behavior = implementation.createOwnedBehavior(opName, UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior |
| |
| op.getMethods().add(behavior) |
| |
| val ConnectorEnd ce = ConnectorUtil.getDelegation(implementation, portInfo.getModelPort()) |
| |
| // if there is an delegation to an inner property, delegate to it |
| // Make distinction between delegation to component (with a port) or |
| // "normal" class (without). |
| var String body |
| if (ce !== null) { |
| val part = ce.partWithPort |
| body = part.name |
| val role = ce.role |
| if (role instanceof Port) { |
| // in case of a delegation, use name of target port which might be different |
| val targetOpName = PrefixConstants.connectQ_Prefix + role.name |
| body = '''«part.nameRef»«targetOpName»(ref)''' |
| |
| } else { |
| // TODO: does this case make sense? |
| body += '''«part.name»;''' |
| } |
| } else { |
| // no delegation - create attribute for port |
| val attributeName = PrefixConstants.attributePrefix + portInfo.name |
| var attr = implementation.getOwnedAttribute(attributeName, null) |
| if (attr === null || attr instanceof Port) { |
| attr = implementation.createOwnedAttribute(attributeName, requiredIntf) |
| CopyUtils.copyMultElemModifiers(portInfo.port, attr) |
| |
| // is shared (should store a reference) |
| attr.setAggregation(AggregationKind.SHARED_LITERAL) |
| } |
| body = attributeName |
| body += " = ref;" |
| } |
| |
| // TODO: defined by template |
| behavior.getLanguages().add(progLang) |
| behavior.getBodies().add(body) |
| |
| // ------------------------- |
| // add body to get-connection operation (which exists already if the port is also |
| // owned, since it is synchronized automatically during model edit) |
| // getConnQ prefix may be empty to indicate that the port is accessed directly |
| // TODO: reconsider optimization that delegated required ports do not have a |
| // local attribute & associated operation (an inner class may delegate, but the |
| // composite may be using it as well). |
| if ((PrefixConstants.getConnQ_Prefix.length() > 0) && (ce !== null)) { |
| val getConnOpName = PrefixConstants.getConnQ_Prefix + portInfo.name |
| var getConnOp = implementation.getOwnedOperation(getConnOpName, null, null) |
| if (getConnOp === null) { |
| getConnOp = implementation.createOwnedOperation(getConnOpName, null, null, requiredIntf) |
| val retParam = op.getOwnedParameters().get(0) |
| retParam.setName(Constants.retParamName) |
| StereotypeUtil.apply(retParam, Ptr) |
| } |
| val getConnBehavior = implementation.createOwnedBehavior(getConnOpName, |
| UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior |
| getConnOp.getMethods().add(getConnBehavior) |
| |
| // no delegation |
| val String name = PrefixConstants.attributePrefix + portInfo.name |
| body = '''return «name»;''' |
| behavior.getLanguages().add(progLang) |
| behavior.getBodies().add(body) |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add an operation "createConnections" that implements the connections |
| * between composite parts. It only takes the assembly connections into |
| * account, since delegation connectors are handled by the get_ and connect_ |
| * port operations above. |
| * |
| * @param implementation |
| */ |
| override addConnectionOperation(Class compositeImplementation) throws TransformationException { |
| var createConnBody = "" |
| val Map<ConnectorEnd, Integer> indexMap = new HashMap<ConnectorEnd, Integer>() |
| |
| for (Connector connector : compositeImplementation.getOwnedConnectors()) { |
| if (ConnectorUtil.isAssembly(connector)) { |
| |
| // Boolean associationBased = false |
| if (connector.ends.size() != 2) { |
| throw new TransformationException( |
| '''Connector <«connector.name»> does not have two ends. This is currently not supported''') |
| } |
| val end1 = connector.ends.get(0) |
| val end2 = connector.ends.get(1) |
| var cmd = '''// realization of connector <«connector.name»>\n''' |
| if ((end1.role instanceof Port) && PortUtils.isExtendedPort(end1.role as Port)) { |
| val port = end1.role as Port |
| val EList<PortInfo> subPorts = PortUtils.flattenExtendedPort(port) |
| for (PortInfo subPort : subPorts) { |
| cmd += ''' // realization of connection for sub-port «subPort.port.name»\n''' |
| cmd += connectPorts(indexMap, connector, end1, end2, subPort.port) |
| cmd += connectPorts(indexMap, connector, end2, end1, subPort.port) |
| } |
| } else { |
| cmd += connectPorts(indexMap, connector, end1, end2, null) |
| cmd += connectPorts(indexMap, connector, end2, end1, null) |
| } |
| createConnBody += cmd + "\n" |
| } |
| } |
| |
| // TODO: use template, as in bootloader |
| if (createConnBody.length() > 0) { |
| val operation = compositeImplementation.createOwnedOperation(Constants.CREATE_CONNECTIONS, null, null) |
| |
| val behavior = compositeImplementation.createOwnedBehavior(operation.name, |
| UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior |
| behavior.getLanguages().add(progLang) |
| behavior.getBodies().add(createConnBody) |
| behavior.setSpecification(operation) |
| } |
| } |
| |
| /** |
| * Create the body C++ code code that creates a connection between the two ends |
| * of a connector. This function checks whether the first end really is a receptacle |
| * and the second really is a facet. |
| * TODO: cleaner rewrite in xtend |
| * |
| * @param indexMap |
| * a map of indices that are used in case of multiplex |
| * receptacles |
| * @param connector |
| * a connector |
| * @param receptacleEnd |
| * an end of the connector that may point to a receptacle port |
| * @param facetEnd |
| * an end of the connector that may point to a facet port |
| * @param subPort |
| * a sub-port in case of extended ports |
| * @return |
| * @throws TransformationException |
| */ |
| static def connectPorts(Map<ConnectorEnd, Integer> indexMap, Connector connector, ConnectorEnd receptacleEnd, |
| ConnectorEnd facetEnd, Port subPort) throws TransformationException { |
| val association = connector.type |
| if ((receptacleEnd.role instanceof Port) && (facetEnd.role instanceof Port)) { |
| val facetPort = facetEnd.role as Port |
| val receptaclePort = receptacleEnd.role as Port |
| val facetPI = PortInfo.fromSubPort(facetPort, subPort) |
| val receptaclePI = PortInfo.fromSubPort(receptaclePort, subPort) |
| |
| if ((facetPI.getProvided() !== null) && (receptaclePI.getRequired() !== null)) { |
| val facetPart = facetEnd.partWithPort |
| val receptaclePart = receptacleEnd.partWithPort |
| |
| var subPortName = "" |
| if(subPort !== null) subPortName += "_" + subPort.name |
| val indexName = getIndexName(indexMap, receptaclePort, receptacleEnd) |
| val setter = '''«receptaclePart.nameRef»connect_«receptaclePort.name» «subPortName»;''' |
| val getter = '''«facetPart.nameRef»get_«facetPort.name» «subPortName»()''' |
| return '''«setter»(«indexName»«getter»);\n''' |
| } |
| |
| } else if (receptacleEnd.role instanceof Port) { |
| |
| // only the receptacle end is of type port. |
| val Port receptaclePort = receptacleEnd.role as Port |
| if (PortUtils.getRequired(receptaclePort) !== null) { |
| val facetPart = facetEnd.role as Property |
| val receptaclePart = facetEnd.partWithPort |
| |
| val indexName = getIndexName(indexMap, receptaclePort, receptacleEnd) |
| val setter = '''«receptaclePart.nameRef»connect_«receptaclePort.name»''' |
| val getter = '''&«facetPart.name»''' |
| return '''«setter»(«indexName»«getter»);\n''' |
| } |
| } else if (facetEnd.role instanceof Port) { |
| |
| // only the facet end is of type port. Unsupported combination |
| val facetPort = facetEnd.role as Port |
| if (PortUtils.getProvided(facetPort) !== null) { |
| val facetPart = facetEnd.partWithPort |
| val receptaclePart = facetEnd.role as Property |
| |
| val setter = receptaclePart.name |
| val getter = '''«facetPart.nameRef»get_«facetPort.name»();''' |
| return '''«setter» = «getter»;\n''' |
| } |
| } else if (association !== null) { |
| |
| // both connector ends do not target ports. In this case, we require that the connector is typed |
| // with an association. We use this association to find out which end is navigable and assume that |
| // the part pointed to by the other end is a pointer that gets initialized with the part of the |
| // navigable end. |
| val facetPart = facetEnd.role as Property |
| val receptaclePart = receptacleEnd.role as Property |
| |
| val assocProp1 = association.getMemberEnd(null, facetPart.type) |
| |
| // Property assocProp2 = facetPart.getOtherEnd() |
| if ((assocProp1 !== null) && assocProp1.isNavigable) { |
| val setter = '''«receptaclePart.nameRef»«assocProp1.name»''' |
| val getter = '''&«facetPart.name»''' |
| return '''«setter» = «getter»;\n''' |
| } |
| } else { |
| |
| // not handled (a connector not targeting a port must be typed) |
| throw new TransformationException( |
| "Connector <" + connector.name + |
| "> does not use ports, but it is not typed (only connectors between ports should not be typed)") |
| } |
| return "" |
| } |
| |
| /** |
| * Handle ports with multiplicity > 1. The idea is that we could have |
| * multiple connections targeting a receptacle. The first connection would |
| * start with index 0. Implementations can make no assumption which |
| * connection is associated with a certain index. [want to avoid associative |
| * array in runtime]. |
| * |
| * @param port |
| * @param end |
| * @return |
| */ |
| static def getIndexName(Map<ConnectorEnd, Integer> indexMap, Port port, ConnectorEnd end) { |
| if ((port.getUpper() > 1) || (port.getUpper() == -1)) { |
| |
| // index depends of combination of property and port, use connector |
| // end as key |
| var indexValue = indexMap.get(end) |
| if (indexValue === null) { |
| indexValue = 0 |
| indexMap.put(end, indexValue) |
| } |
| var index = indexValue + ", " |
| indexValue++ |
| indexMap.put(end, indexValue) |
| return index |
| } |
| return "" |
| } |
| |
| /** |
| * Transform parts if necessary. |
| * |
| * @param compositeImplementation |
| * a (composite) component |
| */ |
| override transformParts(Class compositeImplementation) { |
| |
| /* |
| for (Property attribute : Utils.getParts(compositeImplementation)) { |
| val type = attribute.type |
| if (type instanceof Class) { |
| val cl = type as Class |
| |
| // always transform into pointer (enable dynamic creation) |
| StereotypeUtil.apply(attribute, Ptr) |
| } |
| } |
| */ |
| var String initPartsBody = "" |
| for (Property attribute : PartsUtil.getParts(compositeImplementation)) { |
| val type = attribute.type; |
| if (type instanceof Class) { |
| initPartsBody += initPartBody(attribute); |
| attribute.destroy(); |
| } |
| } |
| |
| val partManager = ElementUtils.getQualifiedElementFromRS(compositeImplementation, PART_MANAGER); |
| if (partManager instanceof Type) { |
| compositeImplementation.createOwnedAttribute(PARTS, partManager); |
| } |
| |
| val operation = compositeImplementation.createOwnedOperation(INIT_PARTS, null, null); |
| |
| val behavior = compositeImplementation.createOwnedBehavior("b:" + operation.name, UMLPackage.eINSTANCE.opaqueBehavior) |
| as OpaqueBehavior |
| behavior.getLanguages().add(progLang); |
| behavior.getBodies().add(initPartsBody); |
| } |
| |
| def initPartBody(Property part) { |
| "parts.add(" + part.getName() + ", " + part.getType() + ")" |
| } |
| } |