| /******************************************************************************* |
| * 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.IASTCompoundStatement; |
| import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; |
| 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.IASTStatement; |
| 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.BasicEList; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.papyrus.designer.languages.common.extensionpoints.ILangCodegen; |
| import org.eclipse.papyrus.designer.languages.common.extensionpoints.ILangCodegen2; |
| 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.listener.ModelListener; |
| 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.ros2.cdteditor.TextEditorConstants; |
| import org.eclipse.papyrus.uml.tools.utils.StereotypeUtil; |
| import org.eclipse.papyrus.uml.tools.utils.UMLUtil; |
| 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.Operation; |
| import org.eclipse.uml2.uml.PackageableElement; |
| import org.eclipse.uml2.uml.Parameter; |
| import org.eclipse.uml2.uml.ParameterDirectionKind; |
| import org.eclipse.uml2.uml.Type; |
| import org.eclipse.uml2.uml.UMLFactory; |
| 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); |
| ModelListener.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); |
| // SyncPortsFromSource.updatePorts(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(); |
| } |
| } |
| } |
| ModelListener.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 = null; |
| if (m_codegen instanceof ILangCodegen2) { |
| syncInfo = ((ILangCodegen2) 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); |
| } |
| } |
| // System.err.println("body source <" + body + ">"); |
| } |
| 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. |
| * |
| * @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(Utils.nsSep); |
| String name = names[names.length - 1]; |
| |
| Operation operation = null; |
| 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; |
| } |
| |
| // Remove all parameters from operation / behavior (they will be added later). |
| // Calling parameters.clear() is not sufficient. Otherwise stereotype |
| // applications to unresolved elements remain in the model |
| List<Parameter> existingParameters = new BasicEList<Parameter>(); |
| UMLUtil.destroyElements(behavior.getOwnedParameters()); |
| if (behavior != null) { |
| for (Parameter existingParameter : behavior.getOwnedParameters()) { |
| Parameter existingParamCopy = UMLFactory.eINSTANCE.createParameter(); |
| existingParamCopy.setName(existingParameter.getName()); |
| existingParamCopy.setType(existingParameter.getType()); |
| existingParamCopy.setDirection(existingParameter.getDirection()); |
| existingParameters.add(existingParamCopy); |
| } |
| } |
| |
| IASTFunctionDefinition definition = (IASTFunctionDefinition) declarator.getParent(); |
| IASTDeclSpecifier declSpecifier = definition.getDeclSpecifier(); |
| Type paramType = getParameterType(null, declSpecifier.toString(), existingParameters); |
| Parameter umlParameter = null; |
| umlParameter = behavior.createOwnedParameter("ret", paramType); |
| umlParameter.setDirection(ParameterDirectionKind.RETURN_LITERAL); |
| // applyParameterModifiers(parameterType, umlParameter, modifiers); |
| |
| 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; |
| } |
| |
| public static String getBody(ITranslationUnit itu, IASTFunctionDefinition definition) { |
| IASTStatement body = definition.getBody(); |
| |
| if (body instanceof IASTCompoundStatement) { |
| IASTCompoundStatement bodyComp = (IASTCompoundStatement) body; |
| |
| IASTFileLocation bodyLoc = bodyComp.getFileLocation(); |
| int start = bodyLoc.getNodeOffset(); |
| int end = start + bodyLoc.getNodeLength(); |
| char contents[] = itu.getContents(); |
| // body contains enclosing { } which we need to remove (+2, -2). We |
| // cannot use the |
| // first and last statement, since leading and trailing comments are |
| // not part of the AST tree. |
| return Utils.decreaseIndent(contents, start + 2, end - 2); |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * 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; |
| } |
| } |