blob: 5787ee132c33cf6ee8d476d1974f423474b19796 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2018 CEA LIST.
*
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Ansgar Radermacher ansgar.radermacher@cea.fr
*
*****************************************************************************/
package org.eclipse.papyrus.robotics.ros2.codegen.utils
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.util.ArrayList
import java.util.Collections
import java.util.HashMap
import java.util.List
import java.util.Map
import org.eclipse.emf.common.util.UniqueEList
import org.eclipse.papyrus.designer.languages.common.profile.Codegen.NoCodeGen
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.External
import org.eclipse.papyrus.designer.transformation.base.utils.TransformationException
import org.eclipse.papyrus.robotics.codegen.common.utils.ApplyProfiles
import org.eclipse.papyrus.robotics.core.utils.InteractionUtils
import org.eclipse.papyrus.robotics.core.utils.PortUtils
import org.eclipse.papyrus.robotics.profile.robotics.services.ServiceDefinitionModel
import org.eclipse.papyrus.robotics.ros2.base.ProcessUtils
import org.eclipse.papyrus.robotics.ros2.base.Ros2Constants
import org.eclipse.papyrus.robotics.ros2.base.Ros2ProcessBuilder
import org.eclipse.papyrus.robotics.ros2.codegen.Activator
import org.eclipse.papyrus.robotics.ros2.codegen.message.CreateMsgPackage
import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil
import org.eclipse.uml2.uml.Class
import org.eclipse.uml2.uml.DataType
import org.eclipse.uml2.uml.Element
import org.eclipse.uml2.uml.Interface
import org.eclipse.uml2.uml.NamedElement
import org.eclipse.uml2.uml.Namespace
import org.eclipse.uml2.uml.Package
import org.eclipse.uml2.uml.Port
import org.eclipse.uml2.uml.TemplateBinding
import org.eclipse.uml2.uml.TemplateParameterSubstitution
import org.eclipse.uml2.uml.Type
import static extension org.eclipse.papyrus.robotics.codegen.common.utils.ComponentUtils.isRegistered
import static extension org.eclipse.papyrus.robotics.codegen.common.utils.Helpers.escapeCamlCase
import static extension org.eclipse.papyrus.robotics.core.utils.InteractionUtils.*
class MessageUtils {
public val static MESSAGE = "msg"
public val static SERVICE = "srv"
public val static ACTION = "action"
/**
* Return the message package, i.e. the first package (navigating up) that applies the
* ServiceDefinitionModel stereotype
*/
def static getMessagePackage(NamedElement ne) {
if (ne === null || ne.eIsProxy) {
return null
}
var Element pkg = ne.nearestPackage
while (pkg instanceof Package) {
if (StereotypeUtil.isApplied(pkg, ServiceDefinitionModel)) {
return pkg as Package
}
pkg = pkg.owner
}
throw new TransformationException(
String.format("Cannot find service definition module for element %s.", ne.name))
}
/**
* Get the qualified ROS2 message name (i.e. packageName/messageKind/messageName)
* from a data type (communication object).
*/
def static getROS2qMsgName(DataType dt) {
var name = dt.name
val pkg = dt.messagePackage
return String.format("%s/%s/%s", pkg.name.toLowerCase, MESSAGE, name)
}
/**
* Return the qualified ROS2 message name (i.e. packageName/messageKind/messageName)
* from a service definition. It removes the prefix from the service definition
* @param sd a service definition
*/
def static getROS2qMsgName(Interface sd) {
val name = sd.nameWoPrefix
val pattern = sd.templateBinding.communicationPattern
var kind = MESSAGE
if (pattern.isQuery) {
kind = SERVICE
} else if (pattern.isAction) {
kind = ACTION
}
val pkg = sd.messagePackage
return String.format("%s/%s/%s", pkg.name.toLowerCase, kind, name)
}
/**
* Return the name of the service definition, without the optional prefix that
* is applied during reverse, i.e. without P_, Q_, S_, and A_
*/
def static getNameWoPrefix(Interface sd) {
val name = sd.name
val pattern = sd.templateBinding.communicationPattern
if (pattern.isPush && name.startsWith("P_")) {
return name.substring(2);
} else if (pattern.isQuery && name.startsWith("Q_")) {
return name.substring(2);
} else if (pattern.isSend && name.startsWith("S_")) {
return name.substring(2);
} else if (pattern.isAction && name.startsWith("A_")) {
return name.substring(2);
}
return name;
}
/**
* Mark a datatype as external (C++ stereotype), since it is used within a message definition and
* ROS will general code for it.
* Instead of its fully qualified name, the type will only be qualified with the ROS2 package
* name
*/
def static void makeDTExternal(DataType dt) {
var external = StereotypeUtil.applyApp(dt, External)
if (external === null) {
ApplyProfiles.applyCppProfile(dt)
external = StereotypeUtil.applyApp(dt, External)
}
val qName = dt.ROS2qMsgName
external.name = qName.replace("/", Namespace.SEPARATOR)
external.incPath = qName.escapeCamlCase + ".hpp"
}
/**
* We should not generate code for the service definition
*/
def static makeSvcDefExternal(TemplateBinding tb) {
val intf = tb.getOwner
StereotypeUtil.applyApp(intf, NoCodeGen)
}
/**
* Transform the service definition into an external ROS2 type.
* This is useful to queries and actions for which the name of the ROS2 service
* or action is used in generated files (not the name of one of the communication
* objects)
*/
def static Interface getServiceType(Port port) {
val tb = port.templateBinding
makeSvcDefExternal(tb)
val sd = tb.serviceDefinition
var external = StereotypeUtil.applyApp(sd, External)
if (external === null) {
ApplyProfiles.applyCppProfile(sd)
external = StereotypeUtil.applyApp(sd, External)
}
// assure that no code (and #include) get generated for the communication pattern
val pattern = tb.communicationPattern
val patternSt = StereotypeUtil.apply(pattern.base_Collaboration, NoCodeGen)
if (patternSt === null) {
ApplyProfiles.applyCommonProfile(pattern.base_Collaboration);
StereotypeUtil.apply(pattern.base_Collaboration, NoCodeGen)
}
val qName = sd.ROS2qMsgName
external.name = qName.replace("/", Namespace.SEPARATOR)
external.incPath = qName.escapeCamlCase + ".hpp"
return sd;
}
/**
* Map a UML primitive type to a native ROS type
*/
def static primitiveTypeMap(Type type) {
// TODO: incomplete
switch type.name {
case "Integer":
"int32"
case "String":
"string"
case "VSL_Expression":
"string" // TODO: more useful mapping
case "Real":
"float64"
case "UnlimitedNatural":
"uint64"
case "Boolean":
"bool"
default:
if (type.name.endsWith("_t") && type.namespace.name.equals("AnsiCLibrary")) {
// handle uintXX_t and others from Ansi-C library
return type.name.substring(0, type.name.length - 2)
} else {
type.name
}
}
}
def static getMsgFileNames(Package msgPackage) {
val msgFileNames = new UniqueEList<String>
for (msg : msgPackage.messages) {
msgFileNames.add(msg.name + ".msg")
}
return msgFileNames
}
def static getSrvFileNames(Package msgPackage) {
val srvFileNames = new UniqueEList<String>
for (sd : msgPackage.queries) {
// use name of service definition, typically consisting of a query and a request
srvFileNames.add(sd.nameWoPrefix + ".srv")
}
return srvFileNames
}
def static getActFileNames(Package msgPackage) {
val actFileNames = new UniqueEList<String>
for (sd : msgPackage.actions) {
// use name of service definition, typically consisting of a query and a request
actFileNames.add(sd.nameWoPrefix + ".action")
}
return actFileNames
}
def static createMessagesOrServices(CreateMsgPackage msgPkgCreator, Class component) {
// val fileAccess = new ProjectBasedFileAccess(TransformationContext.current.project, org.eclipse.papyrus.robotics.ros2.codegen.xtend.CreateMessageUtils.MESSAGE)
// TODO take inherited ports into account?
for (port : component.ownedPorts) {
val tb = InteractionUtils.getTemplateBinding(port)
if (tb !== null) {
msgPkgCreator.createMsgPkgs(tb);
}
}
}
/**
* Obtain communication objects for query - request
*/
def static getRequest(TemplateBinding tb) {
return getActual("Request", tb)
}
/**
* Obtain communication objects for query - response
* or action - reponse
*/
def static getResponse(TemplateBinding tb) {
return getActual("Response", tb)
}
/**
* Obtain communication objects for action - goal
*/
def static getGoal(TemplateBinding tb) {
return getActual("Goal", tb)
}
/**
* Obtain communication objects for action - feedback
*/
def static getFeedback(TemplateBinding tb) {
return getActual("Feedback", tb)
}
/**
* Obtain communication objects for query - request
*/
/**
* Obtain an actual from a given formal
*/
def static getActual(String formalName, TemplateBinding tb) {
for (tps : tb.parameterSubstitutions) {
val actual = tps.actual
if (formalName.equals(tps.formal.getTPName) && actual instanceof NamedElement) {
return actual as NamedElement
}
}
return null
}
/**
* Obtain communication objects for push and send, return first
* message object
*/
def static getMessage(TemplateBinding tb) {
for (tps : tb.parameterSubstitutions) {
val actual = tps.actual
if (actual instanceof NamedElement) {
return actual as NamedElement
}
}
return null
}
def static String getFileName(TemplateBinding tb) {
var String names = null;
for (TemplateParameterSubstitution tps : tb.getParameterSubstitutions()) {
val pe = tps.getActual();
if (pe instanceof NamedElement) {
val name = (pe as NamedElement).getName();
// no underscore separator (ROS2 does not support it)
if (names === null) {
names = name;
} else {
names = names + name;
}
}
}
return names;
}
/**
* Convenience method:
* Return the list of package names that are required by a component
* @param the component
*/
def static List<String> calcDependencies(Class component) {
return calcDependencies(Collections.singletonList(component))
}
/**
* Return the list of package names that are required by a list of components
* @param the component list (a unique list in order to avoid duplicates)
*/
def static List<String> calcDependencies(List<Class> components) {
val list = new UniqueEList<String>
list.add("rclcpp")
if (components.isRegistered) {
list.add("rclcpp_components")
}
list.add("rclcpp_lifecycle")
if (components.haveActions) {
list.add("rclcpp_action")
}
// list.add("message_runtime")
for (pkg : calcUsedMessagePackages(components)) {
val pkgName = pkg.name.toLowerCase
if (!list.contains(pkgName)) {
list.add(pkgName)
}
}
return list
}
/**
* Calculate the message-packages referenced by the messages, services
* and actions of a message package
*/
static def calcDependencies(Package msgPackage) {
val commObjects = new UniqueEList<Type>
val msgPackages = new UniqueEList<Package>
commObjects.addAll(msgPackage.messages)
for (sd : msgPackage.queries) {
commObjects.addAll(sd.templateBinding.commObjects)
}
for (sd : msgPackage.actions) {
commObjects.addAll(sd.templateBinding.commObjects)
}
for (commObject : commObjects) {
msgPackages.add(commObject.messagePackage)
}
// remove dependency to itself
msgPackages.remove(msgPackage)
return msgPackages
}
/**
* Return the list of packages that are required by a list of components
* @param the component list
*/
def static List<Package> calcUsedMessagePackages(List<Class> components) {
val list = new ArrayList<Package>
for (component : components) {
for (port : PortUtils.getAllPorts(component)) {
val pkg = port.serviceDefinition.messagePackage
if (pkg !== null && !list.contains(pkg)) {
list.add(pkg)
}
}
}
return list
}
/**
* return true, if one of the component port is an action port
*/
def static hasActions(Class component) {
for (port : PortUtils.getAllPorts(component)) {
if (port.communicationPattern.isAction) {
return true
}
}
return false;
}
/**
* return true, if one of the components has action ports
*/
def static haveActions(List<Class> components) {
for (component : components) {
if (component.hasActions) {
return true;
}
}
return false;
}
/**
* Return a hash map of available ROS2 package names
* @return the map containing package names
*/
def static Map<String, Boolean> ros2AvailMsgPkgs() {
val pbMsg = new Ros2ProcessBuilder(Ros2Constants.PKG, Ros2Constants.LIST)
val availPackages = new HashMap<String, Boolean>()
try {
val p = pbMsg.start()
val es = p.getErrorStream()
if (es.read() == -1) {
// no input on error stream => check whether output contains package
val results = new BufferedReader(new InputStreamReader(p.inputStream))
var line = ""
var found = false
while ((line = results.readLine()) !== null && !found) {
availPackages.put(line.trim, true)
}
results.close();
} else {
ProcessUtils.logErrors(p);
}
} catch (IOException e) {
Activator.log.error(e)
}
return availPackages
}
}