| /***************************************************************************** |
| * 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 |
| } |
| } |