blob: ef11b916cae78ab73c7dea27e7f34b7be323fc77 [file] [log] [blame]
/*******************************************************************************
* 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Ansgar Radermacher - ansgar.radermacher@cea.fr CEA LIST - initial API and implementation
*
*******************************************************************************/
package org.eclipse.papyrus.robotics.ros2.cdteditor.sync;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IFunctionDeclaration;
import org.eclipse.cdt.core.model.IMethodDeclaration;
import org.eclipse.cdt.core.model.IParent;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.model.IWorkingCopy;
import org.eclipse.cdt.ui.CDTUITools;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.papyrus.designer.languages.common.base.codesync.SyncStatus;
import org.eclipse.papyrus.designer.languages.common.extensionpoints.ILangCodegen;
import org.eclipse.papyrus.designer.languages.common.extensionpoints.SyncInformation;
import org.eclipse.papyrus.designer.languages.cpp.cdt.texteditor.Utils;
import org.eclipse.papyrus.designer.languages.cpp.cdt.texteditor.sync.SyncCDTtoModel;
import org.eclipse.papyrus.designer.languages.cpp.codegen.Constants;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Include;
import org.eclipse.papyrus.infra.core.Activator;
import org.eclipse.papyrus.robotics.core.utils.FileExtensions;
import org.eclipse.papyrus.robotics.core.utils.ScanUtils;
import org.eclipse.papyrus.robotics.ros2.cdteditor.TextEditorConstants;
import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil;
import org.eclipse.ui.IEditorInput;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.BehavioralFeature;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.OpaqueBehavior;
import org.eclipse.uml2.uml.PackageableElement;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.UMLPackage;
public class SyncRoboticsCDTtoModel extends SyncCDTtoModel {
public SyncRoboticsCDTtoModel(IEditorInput input, Classifier classifier, String projectName, String generatorID) {
super(input, classifier, projectName, generatorID);
}
@Override
public void run() {
ICElement ice = CDTUITools.getEditorInputCElement(m_input);
SyncStatus.syncFromEditor = true;
if (ice instanceof ITranslationUnit) {
ICProject project = CoreModel.getDefault().getCModel().getCProject(m_projectName);
IIndex index = null;
try {
index = CCorePlugin.getIndexManager().getIndex(project);
index.acquireReadLock();
ITranslationUnit itu = (ITranslationUnit) ice;
IASTTranslationUnit ast = itu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS);
IASTNodeSelector selector = ast.getNodeSelector(null);
examineChildren(itu, selector, itu);
updateCppInclude(itu);
if (m_classifier instanceof Class) {
List<URI> pathMapURIs = ScanUtils.allPathmapModels(FileExtensions.SERVICEDEF_UML);
SyncPortsToModel.sync((Class) m_classifier, pathMapURIs, ast, itu);
SyncParametersToModel.sync((Class) m_classifier, ast);
}
CUIPlugin.getDefault().getProblemMarkerManager();
if (itu instanceof IWorkingCopy) {
((IWorkingCopy) itu).reconcile(true, new NullProgressMonitor());
}
} catch (CModelException e) {
Activator.log.error(e);
} catch (Exception e) {
Activator.log.error(e);
} finally {
if (index != null) {
index.releaseReadLock();
}
}
}
SyncStatus.syncFromEditor = false;
}
/**
* Examine the children of a translation unit in order to extract the methods
* that are defined within hte unit
*
* @param itu
* @param selector
* @param parent
* @throws CModelException
*/
public void examineChildren(ITranslationUnit itu, IASTNodeSelector selector, IParent parent)
throws CModelException {
int position = 0;
// if (parent instanceof Namespace) {
for (ICElement child : parent.getChildren()) {
if (child instanceof IParent) {
examineChildren(itu, selector, (IParent) child);
}
ISourceRange range = null;
if (child instanceof ISourceReference) {
range = ((ISourceReference) child).getSourceRange();
}
if (child instanceof IFunctionDeclaration) {
// function declaration is a superclass for method declaration
// (but need to trace functions differently?)
String name = ((IFunctionDeclaration) child).getElementName();
IASTNode node = selector.findEnclosingNode(range.getStartPos(), range.getLength());
if (node instanceof IASTFunctionDefinition) {
IASTFunctionDefinition definition = (IASTFunctionDefinition) node;
IASTFunctionDeclarator declarator = definition.getDeclarator();
String unfilteredBody = getBody(itu, definition);
// get additional information about method synchronization from generator
SyncInformation syncInfo = m_codegen.getSyncInformation(name, unfilteredBody);
String body = Utils.removeGenerated(unfilteredBody);
if (syncInfo == null || !syncInfo.isGenerated) {
// only update method, if it is not generated
NamedElement ne = updateMethod(position, parent, name, body, declarator, syncInfo);
if (ne != null) {
updateComment(itu, definition, ne);
}
}
}
position++;
}
}
}
/**
* update the contents of the CppInclude directive
*
* @param itu the translation unit
*/
public void updateCppInclude(ITranslationUnit itu) {
String contents = new String(itu.getContents());
int preBodyStart = contents.indexOf(Constants.cppIncPreBodyStart);
int preBodyEnd = contents.indexOf(Constants.cppIncPreBodyEnd);
String preBody = ""; //$NON-NLS-1$
String body = ""; //$NON-NLS-1$
if (preBodyStart != -1) {
preBodyStart += Constants.cppIncPreBodyStart.length();
if (preBodyEnd > preBodyStart) {
preBody = contents.substring(preBodyStart, preBodyEnd).trim();
}
}
int bodyStart = contents.indexOf(Constants.cppIncBodyStart);
int bodyEnd = contents.indexOf(Constants.cppIncBodyEnd);
if (bodyStart != -1) {
bodyStart += Constants.cppIncBodyStart.length() + 1;
if (bodyEnd > bodyStart) {
body = contents.substring(bodyStart, bodyEnd).trim();
}
}
if (body.length() > 0 || preBody.length() > 0) {
Include include = StereotypeUtil.applyApp(m_classifier, Include.class);
if (include != null) {
include.setPreBody(preBody);
include.setBody(body);
}
}
}
/**
* Update a method in the model based on the qualified name. Unlike the
* overridden method in designer, the code only updates the behavior, not an
* operation. It also only updates the body, not the signature as the latter is
* supposed to be added by the generator based on the function type
*
* @param position The position of the method within the file. Used to
* identify renaming operations
* @param parent the CDT parent which is used to get a list of children
* @param qualifiedName the qualified name of a method
* @param body the method body
* @param declarator the declarator for the method
* @return the operation or the behavior within the model that got updated. The
* latter is returned in case of behaviors that do not have a
* specification (e.g. the effect of a transition).
*/
public NamedElement updateMethod(int position, IParent parent, String qualifiedName, String body,
IASTFunctionDeclarator declarator, SyncInformation syncInfo) {
String names[] = qualifiedName
.split(org.eclipse.papyrus.designer.languages.cpp.cdt.texteditor.TextEditorConstants.nsSep);
String name = names[names.length - 1];
Behavior behavior = null;
if (syncInfo == null || (syncInfo.behavior == null && syncInfo.createBehaviorName == null)) {
behavior = getModelBehaviorFromName(name, parent, position);
if (behavior != null) {
behavior.setName(name);
}
} else if (syncInfo.behavior != null) {
// operation is still null (=> does not enter operation != null case below)
behavior = syncInfo.behavior;
} else if ((syncInfo.createBehaviorName != null) && (m_classifier instanceof Class)) {
Class clazz = (Class) m_classifier;
behavior = (OpaqueBehavior) clazz.createOwnedBehavior(syncInfo.createBehaviorName,
UMLPackage.eINSTANCE.getOpaqueBehavior().eClass());
}
if (behavior == null) {
return null;
}
if (behavior instanceof OpaqueBehavior) {
OpaqueBehavior ob = (OpaqueBehavior) behavior;
if (ob.getBodies().size() == 0) {
ob.getLanguages().add(c_cpp_langID);
ob.getBodies().add(""); //$NON-NLS-1$
}
for (int i = 0; i < ob.getLanguages().size(); i++) {
// update first body of one of the languages supported by CDT. This implies that
// it is actually not possible to have separate C and C++ bodes in the same
// opaque
// behavior (which is rarely a good idea).
String language = ob.getLanguages().get(i);
if (TextEditorConstants.CPP.matcher(language).matches() || c_cpp_langID.equals(language)) {
if (i < ob.getBodies().size()) {
// should always be true, unless sync between
// languages/bodies is lost
ob.getBodies().set(i, body);
}
}
}
}
return behavior;
}
/**
* Obtain a behavior from the model. Similar to method getModelOperationFromName
* in superclass, but Robotics functions are currently defined by opaque
* behaviors
*
* @param name the operation name within CDT
* @param parent the parent of the CDT method within CDT editor model
* @param position the position within the other methods. This information is
* used to locate methods within the model that might have been
* renamed in the CDT editor.
* @return a UML operation
*/
public Behavior getModelBehaviorFromName(String name, IParent parent, int position) {
// operation does not belong to component itself, but to one of the
// activities which in turn consist of functions. Currently, functions are
// defined
// in the package containing the component
PackageableElement beh = m_classifier.getNearestPackage().getPackagedElement(name);
if (beh instanceof OpaqueBehavior) {
return (OpaqueBehavior) beh;
}
List<Behavior> bl = new ArrayList<Behavior>();
for (PackageableElement pe : m_classifier.getNearestPackage().getPackagedElements()) {
if (pe instanceof Behavior) {
bl.add((Behavior) pe);
}
}
// operation is not found via name in the model. try to locate the operation in
// the model at the same
// "position" as the method in the file and
// verify that this method does not have the same name as any method
// in the CDT file.
Behavior be = null;
if (position < bl.size()) {
be = bl.get(position);
String modelName = be.getName();
try {
for (ICElement child : parent.getChildren()) {
if (child instanceof IMethodDeclaration) {
String cdtName = ((IMethodDeclaration) child).getElementName();
if (cdtName.equals(modelName)) {
// an existing operation in the CDT file already
// has this name
be = null;
break;
}
}
}
} catch (CModelException e) {
}
}
return be;
}
/**
* update a comment of a named element. Besides the comment of the element
* itself, comments on contained parameters are handled.
*
* @param itu a translation unit
* @param definition
* @param ne a named element that is either an operation or a behavior
* (in order to update parameters)
*/
public void updateComment(ITranslationUnit itu, IASTFunctionDefinition definition, NamedElement ne) {
IASTFileLocation bodyLoc = definition.getFileLocation();
int start = bodyLoc.getNodeOffset() - 1;
int end = start;
char contents[] = itu.getContents();
String comment = ""; //$NON-NLS-1$
// backward scan for beginning /*
while (start > 0) {
if (contents[start] == '/' && contents[start + 1] == '*') {
start += "/**".length(); // TODO: common string //$NON-NLS-1$
// constants with generator
for (int i = start; i < end; i++) {
comment += contents[i];
}
comment = comment.replace("\n * ", "\n"). //$NON-NLS-1$//$NON-NLS-2$
replace("*/", "").trim(); //$NON-NLS-1$//$NON-NLS-2$
break;
}
start--;
}
if (comment.length() > 0) {
// filter @param
int atParam = comment.indexOf(sAtParam);
String commentMethodOnly = (atParam != -1) ? comment.substring(0, atParam).trim() : comment;
EList<Comment> commentsUML = ne.getOwnedComments();
Comment commentUML;
if (commentsUML.size() == 0) {
commentUML = ne.createOwnedComment();
commentUML.getAnnotatedElements().add(commentUML);
} else {
commentUML = commentsUML.get(0);
}
while (atParam != -1) {
int currentAtParam = atParam;
atParam = comment.indexOf(sAtParam, atParam + 1);
String commentParam = (atParam != -1) ? comment.substring(currentAtParam, atParam)
: comment.substring(currentAtParam);
Comment commentParamUML;
int atParamName = sAtParam.length();
while ((atParamName < commentParam.length())
&& Character.isWhitespace(commentParam.charAt(atParamName))) {
atParamName++;
}
int atParamNameEnd = atParamName;
while ((atParamNameEnd < commentParam.length())
&& !Character.isWhitespace(commentParam.charAt(atParamNameEnd))) {
atParamNameEnd++;
}
if (atParamNameEnd < commentParam.length() - 1) {
String parameterName = commentParam.substring(atParamName, atParamNameEnd);
String commentParamText = commentParam.substring(atParamNameEnd).trim();
Parameter parameter = null;
if (ne instanceof BehavioralFeature) {
parameter = ((BehavioralFeature) ne).getOwnedParameter(parameterName, null, false, false);
} else if (ne instanceof Behavior) {
parameter = ((Behavior) ne).getOwnedParameter(parameterName, null, false, false);
}
if (parameter != null) {
EList<Comment> commentsParamUML = parameter.getOwnedComments();
if (commentsParamUML.size() == 0) {
commentParamUML = parameter.createOwnedComment();
commentParamUML.getAnnotatedElements().add(commentParamUML);
} else {
commentParamUML = commentsParamUML.get(0);
}
commentParamUML.setBody(commentParamText);
} else {
// parameter is not found in model, e.g. either renamed
// or not yet existing
// store comment in operation comment
commentMethodOnly += "\n " + sAtParam + parameterName + //$NON-NLS-1$
" not found(!) " + commentParamText; //$NON-NLS-1$
}
}
}
commentUML.setBody(commentMethodOnly);
}
}
/**
* Accessor
*
* @return value of codegen attribute
*/
public ILangCodegen getCodeGen() {
return m_codegen;
}
}