blob: c2490d029805a66d08bfbb1eda224bfa358fb4af [file] [log] [blame]
/*****************************************************************************
* 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() + ")"
}
}