blob: 94c3dbfca895cdb85470118904f6f90342623e17 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2017 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
* Shuai Li (CEA LIST) <shuai.li@cea.fr> - Bug 530651
* Shuai Li (CEA LIST) <shuai.li@cea.fr> - Bug 531771
* Yoann Farre (CIL4Sys) <yoann.farre@cil4sys.com> - Bug 543072
*
*****************************************************************************/
package org.eclipse.papyrus.designer.components.modellibs.core.transformations
import org.eclipse.papyrus.designer.transformation.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.designer.components.transformation.PortInfo
import org.eclipse.papyrus.designer.components.transformation.PortUtils
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.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.designer.components.transformation.extensions.IOOTrafo
import org.eclipse.papyrus.designer.transformation.base.utils.TransformationException
import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil
import org.eclipse.papyrus.designer.components.FCM.Assembly
import org.eclipse.papyrus.designer.transformation.base.utils.CopyUtils
import org.eclipse.papyrus.designer.components.transformation.component.PrefixConstants
import org.eclipse.papyrus.designer.components.transformation.component.PrefixConstants.CIFvariant
import org.eclipse.uml2.uml.MultiplicityElement
import org.eclipse.uml2.uml.Element
import org.eclipse.papyrus.designer.languages.common.base.ElementUtils
import org.eclipse.uml2.uml.Interface
import org.eclipse.uml2.uml.BehavioredClassifier
import org.eclipse.uml2.uml.Classifier
import java.util.List
/**
* 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. It is an abstract class that is
* refined for C++ and Java
*
* 1. add an operation that allows to retrieve the reference to an interface provided
* by a port. This operation has a mapping to a 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)
*
*/
abstract class AbstractCompToOO implements IOOTrafo {
protected LazyCopier copier
protected String progLang
public static final String NL = "\n"
override init(LazyCopier copier, Class bootloader) {
PrefixConstants.init(CIFvariant.UML);
this.copier = copier
}
/**
* de-reference an attribute. Use language specific mechanism, e.g. attributeName-> in case of a C++ pointer
*/
abstract def String nameRef(Property attribute)
/**
* apply a stereotypes that transforms an elements into a reference, a pointer in case of C++
*/
abstract def void applyRef(Element element)
/**
* Return the reference of an attribute
*/
abstract def String getRef(Property attribute)
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 providedIntfs = portInfo.getProvideds()
if (providedIntfs !== null && !providedIntfs.empty) {
if (providedIntfs.size == 1 && portInfo.port.type instanceof Interface) {
addGetPortOperation(implementation, portInfo, providedIntfs.get(0), portInfo.name);
} else {
for (Interface providedIntf : providedIntfs) {
addGetPortOperation(implementation, portInfo, providedIntf, portInfo.name + providedIntf.name);
}
}
}
}
}
def addGetPortOperation(Class implementation, PortInfo portInfo, Interface providedIntf, String portName) {
// port provides an interface, add "get_p" operation and implementation
val opName = PrefixConstants.getP_Prefix + portName
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)
applyRef(retParam)
if (implementation.isAbstract) {
// since component is abstract, create abstract delegation operation
op.isAbstract = true;
}
else {
addGetPortOperationImpl(implementation, portInfo, providedIntf, portName);
}
}
}
def addGetPortOperationImpl(Class implementation, PortInfo portInfo, Interface providedIntf, String portName) {
val opName = PrefixConstants.getP_Prefix + portName
var op = implementation.getOwnedOperation(opName, null, null)
// op must exist (checked by caller)
val behavior = implementation.createOwnedBehavior(opName,
UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior
op.getMethods().add(behavior)
val ces = ConnectorUtil.getDelegations(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 (!ces.empty) {
body = "return "
var i = 0
while (i < ces.size && body.equals("return ")) {
val part = ces.get(i).partWithPort
val role = ces.get(i).role
if (role instanceof Port) {
val rolePort = role as Port
if (rolePort.provideds.contains(providedIntf)) {
if (rolePort.provideds.size > 1) {
body += '''«part.nameRef»«PrefixConstants.getP_Prefix»«role.name»«providedIntf.name»();'''
} else {
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
if (role instanceof Property) {
val roleType = (role as Property).type;
if (roleType instanceof BehavioredClassifier &&
(roleType as BehavioredClassifier).getInterfaceRealization(null, providedIntf) !== null) {
body += role.name
}
}
}
i++
}
} 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(
"Interface <%s> provided by port <%s> of class <%s> is not implemented by the component itself nor does the port delegate to a part",
providedIntf.name, portName, 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
*/
def addConnectPortOperation(Class implementation) {
for (PortInfo portInfo : PortUtils.flattenExtendedPorts(PortUtils.getAllPorts2(implementation))) {
val requiredIntfs = portInfo.getRequireds()
if (requiredIntfs !== null && !requiredIntfs.empty) {
if (requiredIntfs.size == 1 && portInfo.port.type instanceof Interface) {
addConnectPortOperation(implementation, portInfo, requiredIntfs.get(0), portInfo.name);
} else {
for (Interface requiredIntf : requiredIntfs) {
addConnectPortOperation(implementation, portInfo, requiredIntf,
portInfo.name + requiredIntf.name);
}
}
}
}
}
def addConnectPortOperation(Class implementation, PortInfo portInfo, Interface requiredIntf, String portName) {
// port requires an interface, add "connect_p" operation and implementation
val opName = PrefixConstants.connectQ_Prefix + portName
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 "*"
val refParam = op.createOwnedParameter("ref", requiredIntf)
applyRef(refParam)
val behavior = implementation.createOwnedBehavior(opName,
UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior
op.getMethods().add(behavior)
val ces = ConnectorUtil.getDelegations(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 (!ces.empty) {
var i = 0
while (i < ces.size && body.empty) {
val part = ces.get(i).partWithPort
val role = ces.get(i).role
if (role instanceof Port) {
if ((role as Port).requireds.contains(requiredIntf)) {
body = part.name
// in case of a delegation, use name of target port which might be different
var targetOpName = PrefixConstants.connectQ_Prefix + role.name
if ((role as Port).requireds.size > 1) {
targetOpName += requiredIntf.name
}
body = '''«part.nameRef»«targetOpName»(ref);'''
}
} else {
if (part.type instanceof Classifier &&
(part.type as Classifier).getAllUsedInterfaces().contains(requiredIntf))
body += '''«part.name»;'''
}
i++
}
} else {
// no delegation - create attribute for port
val attributeName = PrefixConstants.attributePrefix + portName
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
if(multiPort) body += "[index]"
body += " = ref;"
}
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 and associated operation (an inner class may delegate, but the
// composite may be using it as well).
if ((PrefixConstants.getConnQ_Prefix.length() > 0) && (!ces.empty)) {
val getConnOpName = PrefixConstants.getConnQ_Prefix + portName
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)
applyRef(retParam)
}
val getConnBehavior = implementation.createOwnedBehavior(getConnOpName,
UMLPackage.eINSTANCE.getOpaqueBehavior()) as OpaqueBehavior
getConnOp.getMethods().add(getConnBehavior)
// no delegation
val String name = PrefixConstants.attributePrefix + portName
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<MultiplicityElement, Integer> indexMap = new HashMap<MultiplicityElement, 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»>''' + NL
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»''' + NL
cmd += connectPorts(connector, end1, end2, subPort.port)
cmd += connectPorts(connector, end2, end1, subPort.port)
}
} else {
cmd += connectPorts(connector, end1, end2, null)
cmd += connectPorts(connector, end2, end1, null)
}
createConnBody += cmd + NL
}
}
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 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.
*
* @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
*/
def connectPorts(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
var result = ""
if (receptaclePI.getRequireds().size > 1) { // receptaclePort requires several interfaces
for (requiredInterface : receptaclePI.getRequireds()) {
var receptaclePortName = receptaclePI.name + requiredInterface.name
var facetPortName = ""
if (facetPI.getProvideds().contains(requiredInterface)) {
facetPortName += facetPI.name
if (facetPI.getProvideds().size > 1) {
facetPortName += requiredInterface.name
}
}
if (!facetPortName.empty) {
val setter = '''«receptaclePart.nameRef»connect_«receptaclePortName»«subPortName»'''
val getter = '''«facetPart.nameRef»get_«facetPortName»«subPortName»()'''
result += '''«setter»(«getter»);''' + NL
}
}
} else { // receptaclePort requires only 1 interface
var facetPortName = ""
if (facetPI.getProvideds().size > 1 &&
facetPI.getProvideds().contains(receptaclePI.getRequired())) {
facetPortName += facetPI.name + receptaclePI.getRequired().name
} else if (facetPI.getProvideds().size == 1) { // Original behavior in case of single interface, where we always connect without check of interface consistency
facetPortName = facetPI.name
}
if (!facetPortName.empty) {
val setter = '''«receptaclePart.nameRef»connect_«receptaclePort.name»«subPortName»'''
val getter = '''«facetPart.nameRef»get_«facetPortName»«subPortName»()'''
result += '''«setter»(«getter»);''' + NL
}
}
return result
}
} 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
var result = ""
if (receptaclePort.getRequireds().size > 1) { // receptaclePort requires several interfaces
for (requiredInterface : receptaclePort.getRequireds()) {
var receptaclePortName = receptaclePort.name + requiredInterface.name
val setter = '''«receptaclePart.nameRef»connect_«receptaclePortName»'''
val getter = '''«facetPart.getRef»'''
result += '''«setter»(«getter»);''' + NL
}
} else {
val setter = '''«receptaclePart.nameRef»connect_«receptaclePort.name»'''
val getter = '''«facetPart.getRef»'''
result += '''«setter»(«getter»);''' + NL
}
return result
}
} 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
var result = ""
if (facetPort.getProvideds().size > 1) { // facetPort provides several interfaces
for (providedInterface : facetPort.getProvideds()) {
var facetPortName = facetPort.name + providedInterface.name
val setter = receptaclePart.name
val getter = '''«facetPart.nameRef»get_«facetPortName»();'''
result += '''«setter» = «getter»;''' + NL
}
} else {
val setter = receptaclePart.name
val getter = '''«facetPart.nameRef»get_«facetPort.name»();'''
result += '''«setter» = «getter»;''' + NL
}
return result
}
} 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)
if ((assocProp1 !== null) && assocProp1.isNavigable) {
var setter = '''«receptaclePart.name».«assocProp1.name»'''
val getter = '''«facetPart.name»'''
return '''«setter» = «getter»;''' + NL
}
} 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 ""
}
/**
* Return true, if the bootloader is responsible for the instantiation of a
* part.
*
* 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]
*
* @param implementation a composite component
* @return
*/
static def instantiateViaBootloader(Class implementation) {
return implementation.isAbstract() || StereotypeUtil.isApplied(implementation, Assembly)
}
/**
* 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
}
/**
* Returns true if the port delegates to multiple parts/ports. Checks in depth.
* @param port
* @return port has multiple delegations in depth
*/
def boolean hasMultipleDelegationsInDepth(Port port) {
val owner = port.owner
if (owner instanceof Class) {
val ces = ConnectorUtil.getDelegations(owner as Class, port)
if (ces.size > 1) {
return true
}
if (ces.size == 1) {
val ce = ces.get(0)
val role = ce.role
if (role instanceof Port) {
return hasMultipleDelegationsInDepth(role as Port)
}
}
}
return false
}
}