| package org.eclipse.papyrus.robotics.ros2.reverse; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.util.List; |
| |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.transaction.RecordingCommand; |
| import org.eclipse.emf.transaction.TransactionalEditingDomain; |
| import org.eclipse.gmf.runtime.common.core.command.ICommand; |
| import org.eclipse.gmf.runtime.notation.Diagram; |
| import org.eclipse.papyrus.designer.languages.common.base.ElementUtils; |
| import org.eclipse.papyrus.designer.languages.cpp.library.CppUriConstants; |
| import org.eclipse.papyrus.infra.core.resource.BadArgumentExcetion; |
| import org.eclipse.papyrus.infra.core.resource.ModelSet; |
| import org.eclipse.papyrus.infra.core.resource.NotFoundException; |
| import org.eclipse.papyrus.infra.core.services.ExtensionServicesRegistry; |
| import org.eclipse.papyrus.infra.core.services.ServiceException; |
| import org.eclipse.papyrus.infra.core.services.ServicesRegistry; |
| import org.eclipse.papyrus.infra.gmfdiag.common.model.NotationModel; |
| import org.eclipse.papyrus.infra.gmfdiag.common.model.NotationUtils; |
| import org.eclipse.papyrus.robotics.core.utils.FileExtensions; |
| import org.eclipse.papyrus.robotics.core.utils.ParameterUtils; |
| import org.eclipse.papyrus.robotics.core.utils.PortUtils; |
| import org.eclipse.papyrus.robotics.core.utils.ScanUtils; |
| import org.eclipse.papyrus.robotics.core.utils.StringUtils; |
| import org.eclipse.papyrus.robotics.profile.robotics.components.ComponentPort; |
| import org.eclipse.papyrus.robotics.ros2.reverse.MessageParser.MessageEntry; |
| import org.eclipse.papyrus.robotics.ros2.reverse.utils.FolderUtils; |
| import org.eclipse.papyrus.robotics.ros2.reverse.utils.ReverseUtils; |
| import org.eclipse.papyrus.uml.diagram.wizards.Activator; |
| import org.eclipse.papyrus.uml.diagram.wizards.command.InitFromTemplateCommand; |
| import org.eclipse.papyrus.uml.diagram.wizards.command.NewPapyrusModelCommand; |
| import org.eclipse.papyrus.uml.tools.model.UmlModel; |
| import org.eclipse.papyrus.uml.tools.model.UmlUtils; |
| import org.eclipse.papyrus.uml.tools.utils.PackageUtil; |
| 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.Interface; |
| import org.eclipse.uml2.uml.NamedElement; |
| import org.eclipse.uml2.uml.Package; |
| import org.eclipse.uml2.uml.Port; |
| import org.eclipse.uml2.uml.Property; |
| import org.eclipse.uml2.uml.Type; |
| |
| @SuppressWarnings("nls") |
| public class ReverseNodes { |
| |
| private static final String MODEL_NAME_UC = "[modelNameUC]"; |
| |
| enum PortKind { |
| PUBSUB, SERVICE, ACTION |
| }; |
| |
| enum PortDirection { |
| PROVIDED, REQUIRED |
| }; |
| |
| public static void reverseNodes(IProgressMonitor monitor) { |
| ProcessBuilder pbMsg = new ProcessBuilder(Ros2Constants.ROS2, Ros2Constants.NODE, Ros2Constants.LIST); |
| readNodeList(pbMsg, monitor); |
| } |
| |
| /** |
| * read the list of nodes and create models. |
| * |
| * @param pb |
| * a process builder object with a suitable ROS2 command |
| */ |
| public static void readNodeList(ProcessBuilder pb, IProgressMonitor monitor) { |
| try { |
| Process p = pb.start(); |
| BufferedReader results = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| StringBuffer errorMsg = new StringBuffer(); |
| boolean error = ReverseUtils.logErrors(p, errorMsg); |
| if (error) { |
| Activator.log.debug(errorMsg.toString()); |
| return; |
| } |
| String line; |
| List<URI> pathMapURIs = ScanUtils.allPathmapModels(FileExtensions.SERVICEDEF_UML); |
| |
| while ((line = results.readLine()) != null) { |
| String frags[] = line.split(MessageParser.SLASH); |
| if (frags.length == 2) { |
| try { |
| String pkgName = frags[0].trim(); |
| String name = frags[1].trim(); |
| // ignore some nodes |
| if (name.endsWith("_rclcpp_node") || name.endsWith("_client_node") || |
| name.startsWith("transform_listener_") || name.startsWith("launch_ros_")) { |
| continue; |
| } |
| |
| monitor.subTask("reverse component " + name); |
| String fileName = name + FileExtensions.COMPDEF_UML; |
| // pass complete filename (a bit abusing the extension attribute) |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| if (pkgName.length() == 0) { |
| pkgName = name; |
| } |
| IProject project = root.getProject(pkgName); |
| IProgressMonitor progressMonitor = new NullProgressMonitor(); |
| if (!project.exists()) { |
| project.create(progressMonitor); |
| } |
| if (!project.isOpen()) { |
| project.open(progressMonitor); |
| } |
| |
| IFolder fModels = FolderUtils.createFolderStructure(project); |
| IFolder fComponents = FolderUtils.getComponentFolder(fModels); |
| IFile fCompModel = fComponents.getFile(fileName); |
| if (!fCompModel.exists()) { |
| |
| ServicesRegistry registry = new ExtensionServicesRegistry(org.eclipse.papyrus.infra.core.Activator.PLUGIN_ID); |
| registry.startServicesByClassKeys(ModelSet.class); |
| |
| ModelSet modelSet = registry.getService(ModelSet.class); |
| TransactionalEditingDomain domain = modelSet.getTransactionalEditingDomain(); |
| |
| URI newURIs = URI.createURI("platform:/resource/" + pkgName + "/models/components/" + fileName); |
| RecordingCommand command = new NewPapyrusModelCommand(modelSet, newURIs); |
| domain.getCommandStack().execute(command); |
| |
| InitFromTemplateCommand tp = new InitFromTemplateCommand(modelSet.getTransactionalEditingDomain(), modelSet, |
| "org.eclipse.papyrus.robotics.wizards", |
| "templates/robotics.compdef.uml", "templates/robotics.compdef.notation", "templates/robotics.compdef.di"); |
| domain.getCommandStack().execute(tp); |
| |
| UmlModel umlModel = UmlUtils.getUmlModel(modelSet); |
| final NotationModel notation = NotationUtils.getNotationModel(modelSet); |
| |
| final Package pkg = (Package) umlModel.lookupRoot(); |
| // load primitive types |
| PackageUtil.loadPackage(URI.createURI(ReverseMessages.PATHMAP_ROS2_PRIMITIVE_UML), pkg.eResource().getResourceSet()); |
| PackageUtil.loadPackage(CppUriConstants.ANSIC_LIB, pkg.eResource().getResourceSet()); |
| |
| final String lineFinal = line; |
| RecordingCommand reverseComponent = new RecordingCommand(domain) { |
| @Override |
| protected void doExecute() { |
| pkg.setName(name); |
| ReverseUtils.setXmlID(pkg); |
| |
| Class clazz = (Class) pkg.getOwnedType(MODEL_NAME_UC); |
| clazz.setName(name); |
| ReverseUtils.setXmlID(clazz); |
| reversePorts(clazz, lineFinal, pathMapURIs); |
| reverseParams(clazz, lineFinal, pathMapURIs); |
| |
| Diagram diagram; |
| try { |
| diagram = notation.getDiagram(MODEL_NAME_UC); |
| final String newName = StringUtils.upperCaseFirst(name) + " diagram"; |
| diagram.setName(newName); |
| } catch (NotFoundException | BadArgumentExcetion e) { |
| e.printStackTrace(); |
| } |
| } |
| }; |
| domain.getCommandStack().execute(reverseComponent); |
| |
| modelSet.save(progressMonitor); |
| registry.disposeRegistry(); |
| } else { |
| // TODO: in the moment, we do not touch existing projects |
| } |
| } catch (CoreException | ServiceException | NotFoundException e) { |
| e.printStackTrace(); |
| } |
| monitor.worked(1); |
| if (monitor.isCanceled()) |
| break; |
| } |
| } |
| results.close(); |
| } catch (IOException exp) { |
| Activator.log.error(exp); |
| } |
| } |
| |
| public static void reversePorts(Class component, String qName, List<URI> pathMapURIs) { |
| ProcessBuilder pb = new ProcessBuilder(Ros2Constants.ROS2, Ros2Constants.NODE, Ros2Constants.INFO, qName); |
| try { |
| Process p = pb.start(); |
| BufferedReader results = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| StringBuffer errorMsg = new StringBuffer(); |
| boolean error = ReverseUtils.logErrors(p, errorMsg); |
| if (error) { |
| Activator.log.debug(errorMsg.toString()); |
| return; |
| } |
| String line; |
| PortKind current = PortKind.PUBSUB; |
| PortDirection direction = PortDirection.PROVIDED; |
| while ((line = results.readLine()) != null) { |
| line = MessageParser.filterComment(line); |
| if (line.contains("Publishers:")) { |
| current = PortKind.PUBSUB; |
| direction = PortDirection.PROVIDED; |
| } else if (line.contains("Subscribers:")) { |
| current = PortKind.PUBSUB; |
| direction = PortDirection.REQUIRED; |
| } else if (line.contains("Service servers:")) { |
| current = PortKind.SERVICE; |
| direction = PortDirection.PROVIDED; |
| } else if (line.contains("Service clients:")) { |
| current = PortKind.SERVICE; |
| direction = PortDirection.REQUIRED; |
| } else if (line.contains("Action servers:")) { |
| current = PortKind.ACTION; |
| direction = PortDirection.PROVIDED; |
| } else if (line.contains("Action clients:")) { |
| current = PortKind.ACTION; |
| direction = PortDirection.REQUIRED; |
| } else { |
| String frags[] = line.split(MessageParser.COLON); |
| if (frags.length == 2) { |
| String qPortName = frags[0].trim(); |
| String qMessageName = frags[1].trim(); |
| MessageEntry entry = MessageParser.extractMessageEntry(qMessageName); |
| if (entry.pkgName.equals("rcl_interfaces") || entry.pkgName.equals("lifecycle")) { |
| // length 3, e.g. "/ROSadder/change_state: lifecycle_msgs/srv/ChangeState" indicate automatically |
| // standard interfaces |
| // TODO - might need info whether a lifecycle node. |
| continue; |
| } |
| if (entry.name.equals("clock")) { |
| continue; |
| } |
| Interface sd = getServiceDef(component, current, entry); |
| if (sd == null) { |
| // check, if the model representing the ROS2 resource is already in the resource set |
| if (ElementUtils.getQualifiedElementFromRS(component, entry.pkgName) == null) { |
| // not loaded => load and retry to get service definition |
| String fileName = ReverseUtils.fileName(entry.pkgName); |
| // load message set from registered packages (TODO: important restriction?) |
| // Cannot use the load function in ReverseUtils, since this loads into the "wrong" resource set |
| for (URI pathURI : pathMapURIs) { |
| if (pathURI.toString().endsWith(fileName)) { |
| component.eResource().getResourceSet().getResource(pathURI, true); |
| sd = getServiceDef(component, current, entry); |
| break; |
| } |
| } |
| } |
| if (sd == null) { |
| Activator.log.debug(String.format("Cannot find service definition for %s", qMessageName)); |
| break; |
| } |
| } |
| String portName = qPortName.trim().substring(1); |
| Port port = component.createOwnedPort(portName, null); |
| ReverseUtils.setXmlID(port); |
| port.setAggregation(AggregationKind.COMPOSITE_LITERAL); |
| StereotypeUtil.apply(port, ComponentPort.class); |
| ICommand csCmd = PortUtils.associateCSCommand(port); |
| csCmd.execute(null, null); |
| Class cs = (Class) port.getType(); |
| if (direction == PortDirection.PROVIDED) { |
| cs.createInterfaceRealization(null, sd); |
| } else { |
| cs.createUsage(sd); |
| } |
| } |
| } |
| } |
| results.close(); |
| |
| } catch (IOException exp) { |
| Activator.log.error(exp); |
| } catch (ExecutionException exp) { |
| Activator.log.error(exp); |
| } |
| } |
| |
| public static void reverseParams(Class component, String qName, List<URI> pathMapURIs) { |
| ProcessBuilder pb = new ProcessBuilder(Ros2Constants.ROS2, Ros2Constants.PARAM, Ros2Constants.LIST, qName); |
| try { |
| Process p = pb.start(); |
| BufferedReader results = new BufferedReader(new InputStreamReader(p.getInputStream())); |
| StringBuffer errorMsg = new StringBuffer(); |
| boolean error = ReverseUtils.logErrors(p, errorMsg); |
| if (error) { |
| Activator.log.debug(errorMsg.toString()); |
| return; |
| } |
| String line; |
| // read list of ROS parameters and add these to the list of parameters of the |
| // describe command |
| ProcessBuilder pbDesc = new ProcessBuilder(Ros2Constants.ROS2, Ros2Constants.PARAM, Ros2Constants.DESCRIBE, qName); |
| while (results.ready() && (line = results.readLine()) != null) { |
| String param = line.trim(); |
| pbDesc.command().add(param); |
| } |
| results.close(); |
| |
| String paramName = ""; |
| Class paramSet = ParameterUtils.getParameterClass(component); |
| // now obtain type |
| Process pDesc = pbDesc.start(); |
| results = new BufferedReader(new InputStreamReader(pDesc.getInputStream())); |
| error = ReverseUtils.logErrors(pDesc, errorMsg); |
| if (error) { |
| Activator.log.debug(errorMsg.toString()); |
| } |
| while (results.ready() && (line = results.readLine()) != null) { |
| String desc = line.trim(); |
| if (desc.startsWith("Parameter name:")) { |
| paramName = desc.substring("Parameter name:".length()).trim(); |
| } |
| if (desc.startsWith("Type:")) { |
| String type = desc.substring(5).trim(); |
| int upper = 1; |
| if (type.equals("string array")) { // TODO - right mapping? |
| type = "string"; |
| upper = -1; |
| } |
| String name = "primitive" + NamedElement.SEPARATOR + type; |
| NamedElement ne = ElementUtils.getQualifiedElementFromRS(component, name); |
| if (ne == null) { |
| if (type.equals("boolean")) { |
| type = "bool"; |
| } |
| if (type.equals("integer")) { |
| type = "int"; |
| } |
| name = "AnsiCLibrary" + NamedElement.SEPARATOR + type; |
| ne = ElementUtils.getQualifiedElementFromRS(component, name); |
| } |
| Property paramUML = paramSet.createOwnedAttribute(paramName, (Type) ne); |
| if (upper != 1) { |
| paramUML.setUpper(upper); |
| } |
| if (ne == null) { |
| Activator.log.debug(String.format("Cannot find type %s", type)); |
| } |
| } |
| } |
| results.close(); |
| |
| } catch (IOException exp) { |
| Activator.log.error(exp); |
| } |
| } |
| |
| public static Interface getServiceDef(Class component, PortKind portKind, MessageEntry entry) { |
| String prefix = ""; |
| if (portKind == PortKind.PUBSUB) { |
| prefix = "P_"; |
| } else if (portKind == PortKind.SERVICE) { |
| prefix = "Q_"; |
| } else if (portKind == PortKind.ACTION) { |
| prefix = "A_"; |
| } |
| String qName = entry.pkgName + NamedElement.SEPARATOR + Ros2Constants.SVCDEFS + NamedElement.SEPARATOR |
| + prefix + entry.name; |
| NamedElement ne = ElementUtils.getQualifiedElementFromRS(component, qName); |
| if (ne instanceof Interface) { |
| return (Interface) ne; |
| } |
| return null; |
| } |
| } |