| /***************************************************************************** |
| * Copyright (c) 2020 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.reverse.fromfile; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; |
| import org.eclipse.cdt.core.dom.ast.IASTExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTFieldReference; |
| import org.eclipse.cdt.core.dom.ast.IASTFileLocation; |
| import org.eclipse.cdt.core.dom.ast.IASTFunctionCallExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; |
| import org.eclipse.cdt.core.dom.ast.IASTIdExpression; |
| import org.eclipse.cdt.core.dom.ast.IASTName; |
| import org.eclipse.cdt.core.dom.ast.IASTNode; |
| import org.eclipse.cdt.core.dom.ast.IASTTypeId; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.dom.ast.ITypedef; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamedTypeSpecifier; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition; |
| import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId; |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.papyrus.designer.languages.common.base.StringUtils; |
| import org.eclipse.papyrus.robotics.ros2.reverse.Activator; |
| import org.eclipse.papyrus.robotics.ros2.reverse.PortInfo; |
| import org.eclipse.papyrus.robotics.ros2.reverse.PortInfo.PortKind; |
| import org.eclipse.papyrus.robotics.ros2.reverse.utils.CreatePortUtils; |
| import org.eclipse.papyrus.robotics.ros2.reverse.utils.ServiceDefUtils; |
| import org.eclipse.uml2.uml.Class; |
| import org.eclipse.uml2.uml.Interface; |
| import org.eclipse.uml2.uml.Namespace; |
| |
| public class ReversePortsFromSource { |
| |
| private static final String CREATE_PUBLISHER = "create_publisher"; //$NON-NLS-1$ |
| private static final String CREATE_SUBSCRIPTION = "create_subscription"; //$NON-NLS-1$ |
| private static final String CREATE_SERVICE = "create_service"; //$NON-NLS-1$ |
| private static final String CREATE_CLIENT = "create_client"; //$NON-NLS-1$ |
| private static final String CREATE_ACTION_SRV = "rclcpp_action::create_server"; //$NON-NLS-1$ |
| private static final String CREATE_ACTION_CLI = "rclcpp_action::create_client"; //$NON-NLS-1$ |
| |
| protected Class clazz; |
| |
| protected List<URI> pathMapURIs; |
| |
| protected ITranslationUnit itu; |
| |
| List<PortInfo> portInfoList; |
| |
| public ReversePortsFromSource(Class clazz, List<URI> pathMapURIs, ITranslationUnit itu) { |
| this.clazz = clazz; |
| this.pathMapURIs = pathMapURIs; |
| this.itu = itu; |
| portInfoList = new ArrayList<PortInfo>(); |
| } |
| |
| public List<PortInfo> getPortInfoList() { |
| return portInfoList; |
| } |
| |
| public void updatePorts(IASTNode node) { |
| scanFunctions(node); |
| |
| for (PortInfo pi : portInfoList) { |
| String nameArray[] = pi.dtQName.split(Namespace.SEPARATOR); |
| String pkgName; |
| String sdName; |
| // we expect that the name is a fully qualified message name <pkgName::[msg|srv]::name> |
| if (nameArray.length > 2) { |
| pkgName = nameArray[0]; |
| sdName = nameArray[2]; |
| } |
| else { |
| // rudimentary handling of other cases (e.g. if project setup is incorrect) |
| pkgName = "*"; //$NON-NLS-1$ |
| sdName = pi.dtQName; |
| } |
| Interface sd = ServiceDefUtils.getServiceDef(clazz, pathMapURIs, pi.pk, pkgName, sdName); |
| if (sd != null) { |
| CreatePortUtils.createPort(clazz, pi, sd); |
| } |
| else { |
| Activator.log.debug(String.format("Cannot find service <%s>", pi.dtQName)); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| public void scanFunctions(IASTNode node) { |
| |
| for (IASTNode child : node.getChildren()) { |
| if (child instanceof IASTFunctionDefinition) { |
| IASTFunctionDefinition definition = (IASTFunctionDefinition) child; |
| scanBody(definition.getBody()); |
| } |
| if (child instanceof ICPPASTNamespaceDefinition) { |
| // recurse into namespaces |
| scanFunctions(child); |
| } |
| } |
| } |
| |
| public static String getASTName(IASTExpression expr) { |
| if (expr instanceof IASTFieldReference) { |
| IASTName fieldName = ((IASTFieldReference) expr).getFieldName(); |
| if (fieldName != null) { |
| return fieldName.toString(); |
| } |
| return ""; //$NON-NLS-1$ |
| } else { |
| return expr.toString(); |
| } |
| } |
| |
| public static PortKind getProviderFromCall(String fctName) { |
| if (fctName.equals(CREATE_CLIENT)) { |
| return PortKind.CLIENT; |
| } else if (fctName.equals(CREATE_SERVICE)) { |
| return PortKind.SERVER; |
| } else if (fctName.equals(CREATE_SUBSCRIPTION)) { |
| return PortKind.SUBSCRIBER; |
| } else if (fctName.equals(CREATE_PUBLISHER)) { |
| return PortKind.PUBLISHER; |
| } else if (fctName.equals(CREATE_ACTION_SRV)) { |
| return PortKind.ACTION_SRV; |
| } else if (fctName.equals(CREATE_ACTION_CLI)) { |
| return PortKind.ACTION_CLI; |
| } |
| return null; |
| } |
| |
| /** |
| * This function obtains the PortInfo from AST data |
| * |
| * @param pk |
| * @param fCallExpr |
| * the function call expression |
| * @param templateId |
| * the template-id |
| */ |
| public PortInfo obtainCallDetails(PortKind pk, IASTFunctionCallExpression fCallExpr, ICPPASTTemplateId templateId) { |
| if (fCallExpr.getArguments().length >= 2) { |
| String topicName = fCallExpr.getArguments()[0].toString(); |
| |
| IASTFileLocation qosLoc = fCallExpr.getArguments()[1].getFileLocation(); |
| |
| char contents[] = itu.getContents(); |
| String qos = new String(contents, qosLoc.getNodeOffset(), qosLoc.getNodeLength()); |
| |
| PortInfo portInfo = new PortInfo(); |
| portInfo.pk = pk; |
| IASTNode argument = templateId.getTemplateArguments()[0]; |
| if (argument instanceof IASTTypeId) { |
| IASTDeclSpecifier declSpecifier = ((IASTTypeId) argument).getDeclSpecifier(); |
| if (declSpecifier instanceof ICPPASTNamedTypeSpecifier) { |
| // handle case that type is defined via "using" statement |
| IBinding binding = ((ICPPASTNamedTypeSpecifier) declSpecifier).getName().resolveBinding(); |
| // but exclude typedef (as all messages or services are typedefs |
| if (binding != null && !(binding instanceof ITypedef)) { |
| portInfo.dtQName = binding.toString(); |
| } |
| } |
| if (portInfo.dtQName == null) { |
| portInfo.dtQName = ((IASTTypeId) argument).getDeclSpecifier().toString(); |
| } |
| } else { |
| Activator.log.debug("Unexpected template argument"); //$NON-NLS-1$ |
| } |
| portInfo.topic = StringUtils.unquote(topicName.trim()); |
| portInfo.qos = qos.trim(); |
| return portInfo; |
| } |
| return null; |
| } |
| |
| public void scanBody(IASTNode node) { |
| if (node instanceof IASTFunctionCallExpression) { |
| IASTFunctionCallExpression fCallExpr = (IASTFunctionCallExpression) node; |
| IASTExpression nameExpr = fCallExpr.getFunctionNameExpression(); |
| if (nameExpr instanceof IASTIdExpression) { |
| IASTIdExpression nameId = (IASTIdExpression) nameExpr; |
| IASTName astName = nameId.getName(); |
| if (astName instanceof ICPPASTTemplateId) { |
| // all ROS2 "ports" are created by binding one or more concrete message types |
| // e.g. create_publisher<geometry_msgs::msg::PoseArray>(...) |
| ICPPASTTemplateId templateId = (ICPPASTTemplateId) astName; |
| String fctName = templateId.getTemplateName().toString(); |
| PortKind pk = getProviderFromCall(fctName); |
| if (pk != null) { |
| PortInfo portInfo = obtainCallDetails(pk, fCallExpr, templateId); |
| if (portInfo != null) { |
| portInfoList.add(portInfo); |
| } |
| } |
| } |
| } |
| } |
| for (IASTNode child : node.getChildren()) { |
| // recurse into all children |
| scanBody(child); |
| } |
| } |
| } |