blob: c1418e9a42ccd475ac79f8df173df30e236e207d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 CEA LIST.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ansgar Radermacher - ansgar.radermacher@cea.fr CEA LIST - initial API and implementation
*
*******************************************************************************/
package org.eclipse.papyrus.designer.languages.cpp.cdt.texteditor.sync;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.ast.ExpansionOverlapsBoundaryException;
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.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTNodeSelector;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
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.core.parser.IToken;
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.papyrus.designer.languages.common.extensionpoints.ILangCodegen;
import org.eclipse.papyrus.designer.languages.common.extensionpoints.ILangCodegen2;
import org.eclipse.papyrus.designer.languages.common.extensionpoints.LanguageCodegen;
import org.eclipse.papyrus.designer.languages.common.extensionpoints.SyncInformation;
import org.eclipse.papyrus.designer.languages.cpp.cdt.texteditor.TextEditorConstants;
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.codegen.Constants;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Array;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Const;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.EStorageClass;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Include;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Ptr;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Ref;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.StorageClass;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Volatile;
import org.eclipse.papyrus.designer.transformation.base.utils.CommandSupport;
import org.eclipse.papyrus.infra.core.Activator;
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.DataType;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.OpaqueBehavior;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UMLPackage;
public class SyncCDTtoModel implements Runnable {
public static final String REGISTER = "register"; //$NON-NLS-1$
public static final String CONST = "const"; //$NON-NLS-1$
public static final String VOLATILE = "volatile"; //$NON-NLS-1$
public static final String sAtParam = "@param"; //$NON-NLS-1$
public static final String ansiCLib = "AnsiCLibrary"; //$NON-NLS-1$
public SyncCDTtoModel(IEditorInput input, Classifier classifier, String projectName, String generatorID) {
m_input = input;
m_classifier = classifier;
m_projectName = projectName;
m_codegen = LanguageCodegen.getGenerator(TextEditorConstants.CPP, generatorID);
}
public final String c_cpp_langID = "C/C++"; //$NON-NLS-1$
public void syncCDTtoModel() {
CommandSupport.exec(m_classifier, "update model from CDT", this);
}
@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();
// index = CCorePlugin.getIndexManager().getIndex(project);
ITranslationUnit itu = (ITranslationUnit) ice;
// hack: force re-evaluation of AST node, requires modified CDT!
// Seems to be no longer required.
// ASTProvider.getASTProvider().fCache.setActiveElement(itu);
IASTTranslationUnit ast = itu.getAST(index, ITranslationUnit.AST_SKIP_INDEXED_HEADERS);
IASTNodeSelector selector = ast.getNodeSelector(null);
examineChildren(itu, selector, itu);
updateCppInclude(itu);
CUIPlugin.getDefault().getProblemMarkerManager();
if (itu instanceof IWorkingCopy) {
// ((IWorkingCopy)itu).commit(true, new
// NullProgressMonitor());
// ((IWorkingCopy)itu).reconcile();
((IWorkingCopy) itu).reconcile(true, new NullProgressMonitor());
// ((IWorkingCopy)itu).reconcile(true, true, new
// NullProgressMonitor());
}
} catch (CModelException e) {
Activator.getDefault().getLog().log(e.getStatus());
} catch (Exception e) {
System.err.println(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);
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)) {
operation = getModelOperationFromName(name, parent, position);
if (operation != null) {
operation.setName(name);
} else {
// it is possible that the C++ method corresponds to the effect
// of a transition. try to locate the behavior (without using an operation)
behavior = FindTransition.findBehavior(m_classifier, name);
if (behavior == null) {
// still null => create new operation in model
if (m_classifier instanceof Class) {
operation = ((Class) m_classifier).createOwnedOperation(name, null, null);
} else if (m_classifier instanceof DataType) {
operation = ((DataType) m_classifier).createOwnedOperation(name, null, null);
}
}
}
}
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 (operation != null) {
if (operation.getMethods().size() == 0) {
// operation exists, but does not have any method => create
if (m_classifier instanceof Class) {
behavior = ((Class) m_classifier).createOwnedBehavior(name, UMLPackage.eINSTANCE.getOpaqueBehavior());
} else if (m_classifier instanceof DataType) {
// ob = (OpaqueBehavior) ((DataType)
// m_classifier).createOwnedBehavior(name,
// UMLPackage.eINSTANCE.getOpaqueBehavior());
}
behavior.setSpecification(operation);
behavior.setIsReentrant(false);
}
else {
behavior = operation.getMethods().get(0);
// operation has at least one method, this may not be null.
if (!behavior.getName().equals(name)) {
behavior.setName(name);
}
}
}
// assertions: operation can be null, behavior is always non-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
if (operation != null) {
UMLUtil.destroyElements(operation.getOwnedParameters());
}
UMLUtil.destroyElements(behavior.getOwnedParameters());
for (IASTNode declaratorChild : declarator.getChildren()) {
if (declaratorChild instanceof IASTParameterDeclaration) {
IASTParameterDeclaration parameter = (IASTParameterDeclaration) declaratorChild;
IASTName parameterName = parameter.getDeclarator().getName();
IASTDeclSpecifier parameterType = parameter.getDeclSpecifier();
ParameterModifiers modifiers = new ParameterModifiers();
String parameterTypeName = ""; //$NON-NLS-1$
try {
IToken token = parameter.getDeclarator().getSyntax();
while (token != null) {
String tokenStr = token.toString();
if (tokenStr.equals("*")) { //$NON-NLS-1$
modifiers.isPointer = true;
} else if (tokenStr.equals("&")) { //$NON-NLS-1$
modifiers.isRef = true;
} else if (tokenStr.equals("[")) { //$NON-NLS-1$
while (token != null) {
modifiers.array += token.toString();
token = token.getNext();
}
if (token == null) {
break;
}
}
token = token.getNext();
}
token = parameterType.getSyntax();
while (token != null) {
String tokenStr = token.toString();
if (tokenStr.equals("*")) { //$NON-NLS-1$
// TODO: check, if this can be called (depending on
// * position with different semantics?)
modifiers.isPointer = true;
} else if (tokenStr.equals("&")) { //$NON-NLS-1$
modifiers.isRef = true;
} else if (tokenStr.equals(REGISTER)) {
modifiers.isRegister = true;
} else if (tokenStr.equals(CONST) || tokenStr.equals(VOLATILE)) {
// do nothing (use isConst() or isVolatile() operation of parameterType)
// is not part of parameter type
} else {
if (parameterTypeName.length() > 0) {
parameterTypeName += " "; //$NON-NLS-1$
}
parameterTypeName += tokenStr;
}
token = token.getNext();
}
} catch (ExpansionOverlapsBoundaryException e) {
}
NamedElement namedElemParamType = Utils.getQualifiedElement(Utils.getTop(m_classifier),
parameterTypeName);
if (namedElemParamType == null) {
namedElemParamType = Utils.getQualifiedElement(Utils.getTop(m_classifier),
ansiCLib + Utils.nsSep + parameterTypeName);
}
if (parameterType.isRestrict()) {
}
Parameter umlParameter = null;
Type paramType = namedElemParamType instanceof Type ? (Type) namedElemParamType : null;
if (operation != null) {
umlParameter = operation.createOwnedParameter(parameterName.toString(), paramType);
applyParameterModifiers(parameterType, umlParameter, modifiers);
}
umlParameter = behavior.createOwnedParameter(parameterName.toString(), paramType);
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);
}
}
}
}
if (operation != null) {
return operation;
}
else {
return behavior;
}
}
/**
* Apply the modifiers for a parameter, notably the stereotypes of the C++ profile
*
* @param parameterType the CDT AST parameter specification
* @param umlParameter the UML parameter (to which a stereotype should be applied)
* @param modifiers the modifiers that should be applied (stored in an instance of class ParameterModifiers)
*/
public void applyParameterModifiers(IASTDeclSpecifier parameterType, Parameter umlParameter, ParameterModifiers modifiers) {
if (parameterType.isConst()) {
StereotypeUtil.apply(umlParameter, Const.class);
}
if (parameterType.isVolatile()) {
StereotypeUtil.apply(umlParameter, Volatile.class);
}
if (modifiers.isRegister) {
StorageClass sc = StereotypeUtil.applyApp(umlParameter, StorageClass.class);
if (sc != null) {
sc.setStorageClass(EStorageClass.REGISTER);
}
}
if (modifiers.isPointer) {
StereotypeUtil.apply(umlParameter, Ptr.class);
} else if (modifiers.isRef) {
StereotypeUtil.apply(umlParameter, Ref.class);
}
if (modifiers.array.length() > 0) {
Array arraySt = StereotypeUtil.applyApp(umlParameter, Array.class);
if (arraySt != null && !modifiers.array.equals("[]") && (!modifiers.array.equals("[ ]"))) { //$NON-NLS-1$//$NON-NLS-2$
arraySt.setDefinition(modifiers.array);
}
}
}
/**
* Obtain an operation from the model by using the name of a CDT method.
* If an operation of the given name does not exist, it might indicate that
* the method has been renamed.
*
* @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
*/
public Operation getModelOperationFromName(String name, IParent parent, int position) {
Operation operation = m_classifier.getOperation(name, null, null);
if (operation == null) {
// 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.
if (position < m_classifier.getOperations().size()) {
operation = m_classifier.getOperations().get(position);
String modelName = operation.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
operation = null;
break;
}
}
}
} catch (CModelException e) {
}
}
}
return operation;
}
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;
}
/**
* input of the CDT editor. Used to obtain code within editor.
*/
protected IEditorInput m_input;
/**
* The classifier (class) that is currently edited
*/
protected Classifier m_classifier;
/**
* name of CDT project in which the generated code is stored.
*/
protected String m_projectName;
/**
* reference to code generator
*/
protected ILangCodegen m_codegen;
}