Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'extraplugins/qompass-designer/org.eclipse.papyrus.qompass.designer.cpp/src/org/eclipse/papyrus/qompass/designer/cpp/xtend/CppPortMapping.xtend')
-rw-r--r--extraplugins/qompass-designer/org.eclipse.papyrus.qompass.designer.cpp/src/org/eclipse/papyrus/qompass/designer/cpp/xtend/CppPortMapping.xtend552
1 files changed, 552 insertions, 0 deletions
diff --git a/extraplugins/qompass-designer/org.eclipse.papyrus.qompass.designer.cpp/src/org/eclipse/papyrus/qompass/designer/cpp/xtend/CppPortMapping.xtend b/extraplugins/qompass-designer/org.eclipse.papyrus.qompass.designer.cpp/src/org/eclipse/papyrus/qompass/designer/cpp/xtend/CppPortMapping.xtend
new file mode 100644
index 00000000000..238f4e81035
--- /dev/null
+++ b/extraplugins/qompass-designer/org.eclipse.papyrus.qompass.designer.cpp/src/org/eclipse/papyrus/qompass/designer/cpp/xtend/CppPortMapping.xtend
@@ -0,0 +1,552 @@
+/*****************************************************************************
+ * 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 v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Ansgar Radermacher ansgar.radermacher@cea.fr
+ *
+ *****************************************************************************/
+package org.eclipse.papyrus.qompass.designer.cpp.xtend
+
+import org.eclipse.papyrus.qompass.designer.core.extensions.IOOTrafo
+import org.eclipse.papyrus.qompass.designer.core.transformations.LazyCopier
+import org.eclipse.uml2.uml.Class
+import org.eclipse.uml2.uml.Property
+import org.eclipse.uml2.uml.Port
+import org.eclipse.papyrus.qompass.designer.core.transformations.TransformationException
+import org.eclipse.papyrus.qompass.designer.core.PortInfo
+import org.eclipse.papyrus.qompass.designer.core.PortUtils
+import org.eclipse.papyrus.qompass.designer.core.transformations.PrefixConstants
+import org.eclipse.papyrus.qompass.designer.core.Utils
+import org.eclipse.papyrus.qompass.designer.core.transformations.CompTypeTrafos
+import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil
+import org.eclipse.uml2.uml.AggregationKind
+import org.eclipse.uml2.uml.UMLPackage
+import org.eclipse.uml2.uml.OpaqueBehavior
+import org.eclipse.uml2.uml.ConnectorEnd
+import org.eclipse.papyrus.uml.tools.utils.ConnectorUtil
+import org.eclipse.papyrus.C_Cpp.Ptr
+import org.eclipse.uml2.uml.Type
+import java.util.HashMap
+import java.util.Map
+import org.eclipse.uml2.uml.Connector
+import org.eclipse.emf.common.util.EList
+import org.eclipse.uml2.uml.StructuralFeature
+import org.eclipse.papyrus.qompass.designer.cpp.Messages
+import org.eclipse.papyrus.qompass.designer.cpp.Constants
+import static extension org.eclipse.papyrus.qompass.designer.cpp.xtend.CppUtils.nameRef;
+
+/**
+ * This class realizes the transformation from component-based to object-oriented
+ * models. It includes the replacement of ports and connectors. Ports are
+ * replaced with attributes and access operations, connectors within a composite
+ * by an operation that creates the initial setup.
+ *
+ * 1. add an operation that allows to retrieve the reference to an interface provided
+ * by a port. This operation has a mapping specific name, e.g. get_<port_name>
+ * 2. add an operation that allows to connect a specific port.
+ * the connect_q operation (*including a
+ * storage attribute*) for a port with a required interface
+ * 3. add an implementation for the getcnx_q operation for a port
+ * with a required interface (the operation itself has been added before)
+ *
+ * TODO: C++ specific, support different "component to OO" mappings
+ *
+ * Problems: need to align bootloader creation with this mapping, since
+ * the bootloader may be responsible for instantiation
+ *
+ * Caveat: Assure that the folder derivedInterfaces already exists in a model.
+ * Otherwise the call to getProvided/getRequired interface might trigger its
+ * creation resulting in the corruption of list iterators (ConcurrentAccess
+ * exception)
+ *
+ */
+class CppPortMapping implements IOOTrafo {
+
+ // protected LazyCopier copier
+
+ def override init(LazyCopier copier, Class bootloader) {
+ // this.copier = copier
+ }
+
+ 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) {
+ // TODO: This is a hack/workaround. Fix on level of extended port.
+ // 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(Constants.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 boolean multiPort = (portInfo.getUpper() > 1) || (portInfo.getUpper() == -1) // -1 indicates "*"
+ if (multiPort) {
+
+ // add index parameter
+ val eLong = Utils.getQualifiedElement(Utils.getTop(implementation),
+ CompTypeTrafos.INDEX_TYPE_FOR_MULTI_RECEPTACLE)
+ if (eLong instanceof Type) {
+ op.createOwnedParameter("index", eLong as Type)
+ } else {
+ throw new RuntimeException(
+ String.format(Messages.CompImplTrafos_CannotFindType,
+ CompTypeTrafos.INDEX_TYPE_FOR_MULTI_RECEPTACLE))
+ }
+ }
+ 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»'''
+
+ // TODO: no check that multiplicity of both port matches
+ if ((portInfo.getUpper() > 1) || (portInfo.getUpper() == -1)) {
+ body += "(index, ref);";
+ }
+ else {
+ body += "(ref);";
+ }
+
+ } else {
+ // TODO: does this case make sense?
+ body += '''«part.name»;'''
+ }
+ } else {
+ // no delegation - create attribute for port
+ val attributeName = PrefixConstants.attributePrefix + portInfo.name
+ if (!Utils.hasNonPortOwnedAttribute(implementation, attributeName)) {
+ val attr = implementation.createOwnedAttribute(attributeName, requiredIntf)
+ LazyCopier.copyMultElemModifiers(portInfo.port, attr)
+
+ // is shared (should store a reference)
+ attr.setAggregation(AggregationKind.SHARED_LITERAL)
+ }
+ body = attributeName
+ if(multiPort) body += "[index]"
+ body += " = ref;"
+ }
+
+ // TODO: defined by template
+ behavior.getLanguages().add(Constants.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(Constants.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("b:" + operation.name,
+ UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior
+ behavior.getLanguages().add(Constants.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 ""
+ }
+
+ /**
+ * Return true, if the bootloader is responsible for the instantiation of a
+ * part. [Structual difference: bootloader can decide instance based - and
+ * instances are deployed]
+ *
+ * If a part is a component type or an abstract implementation, it cannot be
+ * instantiated. Thus, a heir has to be selected in the deployment plan.
+ * Since the selection might be different for different instances of the
+ * composite, the instantiation is not done by the component itself, but by
+ * the bootloader. The bootloader also has to instantiate, if different
+ * allocation variants are required. (this is for instance the case for
+ * distribution connectors and for the system itself)
+ *
+ * If possible, we want to let composites instantiate sub-components, since
+ * this eases the transition to systems which support reconfiguration.
+ *
+ * [TODO: optimization: analyze whether the deployment plan selects a single
+ * implementation. If yes, let the composite instantiate]
+ *
+ * [TODO: elements within an assembly need to be instantiated by composite -
+ * if System - by bootloader. assembly also need to be instantiated by
+ * composite!!
+ *
+ * @param implementation
+ * @return
+ */
+ static def instantiateViaBootloader(Class implementation) {
+ return implementation.isAbstract() || Utils.isAssembly(implementation)
+ }
+
+ /**
+ * Return whether a part needs to be instantiated by the bootloader instead
+ * by the composite in which it is contained. The criteria is based on the
+ * question whether the containing composite is flattened, as it is the case
+ * for the system component and the interaction components for distribution.
+ *
+ * @param part
+ * @return
+ */
+ static def instantiateViaBootloader(StructuralFeature part) {
+ if (part != null) {
+ if (part.type instanceof Class) {
+ val implementation = part.type as Class
+
+ // TODO: wrong criteria? (must be shared or not?)
+ return instantiateViaBootloader(implementation)
+ } else {
+
+ // not a class, assume primitive type instantiated by composite
+ return false
+ }
+ }
+ return false
+ }
+
+ /**
+ * Transform parts if necessary.
+ *
+ * If the bootloader is responsible for creating an instance (if it is a
+ * abstract type), mark the associated part as a C++ pointer. We do not want
+ * to change the aggregation kind, since it remains logically a composition,
+ * it is merely an implementation issue that it must be a pointer for C++ if
+ * the concrete type is not yet known.
+ *
+ * @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
+
+ // => requires adaptations of boot-loader which is then only
+ // responsible for creating instances corresponding to types
+ if (instantiateViaBootloader(cl)) {
+ StereotypeUtil.apply(attribute, Ptr)
+ }
+ }
+ }
+ }
+}

Back to the top