blob: 0c8ced66230bc9bf4b7db6be591becad7b29659f [file] [log] [blame]
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;
}
}