blob: fae2134a1fc9581fa0b1a9cb02e620d480fc331c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 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:
* CEA LIST - initial API and implementation
*******************************************************************************/
package org.eclipse.papyrus.designer.languages.c.codegen.transformation;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.ToolFactory;
import org.eclipse.cdt.core.formatter.CodeFormatter;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.papyrus.designer.infra.base.StringConstants;
import org.eclipse.papyrus.designer.languages.c.codegen.Activator;
import org.eclipse.papyrus.designer.languages.c.codegen.header.ClassHeaderScript;
import org.eclipse.papyrus.designer.languages.c.codegen.module.ClassModuleScript;
import org.eclipse.papyrus.designer.languages.c.codegen.preferences.CCodeGenUtils;
import org.eclipse.papyrus.designer.languages.common.base.GenUtils;
import org.eclipse.papyrus.designer.languages.common.base.ModelElementsCreator;
import org.eclipse.papyrus.designer.languages.common.base.file.FileSystemAccessFactory;
import org.eclipse.papyrus.designer.languages.common.base.file.IFileExists;
import org.eclipse.papyrus.designer.languages.common.base.file.IPFileSystemAccess;
import org.eclipse.papyrus.designer.languages.common.profile.Codegen.NoCodeGen;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.CppRoot;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.External;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Include;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.ManualGeneration;
import org.eclipse.papyrus.designer.languages.cpp.profile.C_Cpp.Template;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Classifier;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Enumeration;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Namespace;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.PackageableElement;
import org.eclipse.uml2.uml.PrimitiveType;
import org.eclipse.uml2.uml.Relationship;
import org.eclipse.uml2.uml.Signal;
import org.eclipse.uml2.uml.SignalEvent;
import org.eclipse.uml2.uml.Usage;
import org.eclipse.uml2.uml.util.UMLUtil;
/**
* Main class of code generator
*/
public class CModelElementsCreator extends ModelElementsCreator {
private static final String C_LANG = "C"; //$NON-NLS-1$
protected String sourceFolder;
protected boolean formatCode;
/**
* Constructor.
*
* @param project
* the project in which the generated code should be placed
*/
public CModelElementsCreator(IProject project) {
this(project, FileSystemAccessFactory.create(project), null);
}
/**
* Constructor, allows for non-standard commentHeader
*
* @param project
* the project in which the generated code should be placed
* @param commentHeader
* Custom prefix for each generated file
*/
public CModelElementsCreator(IProject project, String commentHeader) {
this(project, FileSystemAccessFactory.create(project), commentHeader);
}
/**
* Constructor. Pass caller defined file system access and commentHeader
*
* @param fileSystemAccess
* the file-system access used to write generated code
* @param commentHeader
* commentHeader. If null, take from preferences
*/
public CModelElementsCreator(IPFileSystemAccess fileSystemAccess, String commentHeader) {
this(null, fileSystemAccess, commentHeader);
}
/**
* Constructor. Pass caller defined file system access and commentHeader
*
* @param project
* a project (used for project-specific formatting)
* @param fileSystemAccess
* the file-system access used to write generated code
* @param commentHeader
* commentHeader. If null, take from preferences
*/
public CModelElementsCreator(IProject project, IPFileSystemAccess fileSystemAccess, String commentHeader) {
super(fileSystemAccess, new CLocationStrategy(), C_LANG);
this.project = project;
this.commentHeader = (commentHeader != null) ? commentHeader : CCodeGenUtils.getCommentHeader();
hExt = CCodeGenUtils.getHeaderSuffix();
cExt = CCodeGenUtils.getBodySuffix();
formatCode = CCodeGenUtils.getFormatCode();
sourceFolder = null;
}
protected String hExt;
protected String cExt;
protected String commentHeader;
/**
* Creates the files corresponding to the class. For a "simple" class generates
* 2 headers (one for the privates concrete operations and one for the
* attributes, public operations and virtual / abstract operations and one body
* file.
*
* @param folder
* @param classifier
* @throws CoreException
*/
@Override
protected void createPackageableElementFile(PackageableElement element, IProgressMonitor monitor) {
if (sourceFolder == null) {
sourceFolder = GenUtils.getSourceFolder(element);
}
if (element instanceof Package) {
// generatePkg((Package) element);
}
else if ((element instanceof PrimitiveType) || (element instanceof Enumeration) || (element instanceof Usage)) {
// do nothing, included in package
} else if (element instanceof Classifier) {
generateClassifier((Classifier) element);
} else if (element instanceof Relationship) {
// no code generation for relationships
} else if (element instanceof Signal) {
// TODO: not supported, but do nothing
} else if (element instanceof SignalEvent) {
// TODO: not supported, but do nothing
} else {
Activator.log.debug("C_LANG code generator: unsupported model element " + element); //$NON-NLS-1$
}
}
/**
* Creates the files corresponding to the class. For a "simple" class generates
* 2 headers (one for the privates concrete operations and one for the
* attributes, public operations and virtual / abstract operations and one body
* file.
*
* @param folder
* @param classifier
* @throws CoreException
*/
protected void generateClassifier(Classifier classifier) {
String dot = "."; //$NON-NLS-1$
String LF = "\n"; //$NON-NLS-1$
final String classHeaderFileName = getFileName(classifier) + dot + hExt;
final String classBodyFileName = getFileName(classifier) + dot + cExt;
// treat case of manual code generation
if (GenUtils.hasStereotype(classifier, ManualGeneration.class)) {
ManualGeneration mg = UMLUtil.getStereotypeApplication(classifier, ManualGeneration.class);
Include cppInclude = UMLUtil.getStereotypeApplication(classifier, Include.class);
String fileContent = commentHeader + cppInclude.getHeader();
generateFile(classHeaderFileName, fileContent);
fileContent = commentHeader + cppInclude.getPreBody() + LF + cppInclude.getBody();
String ext = GenUtils.maskNull(mg.getExtensionBody());
if (ext.length() == 0) {
ext = cExt;
}
generateFile(classBodyFileName, fileContent);
}
// Only generate when no CppNoCodeGen stereotype is applied to the class
else if ((!GenUtils.hasStereotype(classifier, NoCodeGen.class))
&& (!GenUtils.hasStereotype(classifier, External.class))
&& (!GenUtils.hasStereotype(classifier, Template.class))) {
// Header file generation
String fileContent = commentHeader + ClassHeaderScript.classHeaderScript((Class) classifier);
generateFile(classHeaderFileName, fileContent);
// Create class body
if (classifier instanceof Class) {
fileContent = commentHeader + ClassModuleScript.classModuleScript((Class) classifier);
generateFile(classBodyFileName, fileContent);
}
}
}
/**
* Obtain fileName of file(s) generated for a named element. Overrides
* superclass definition to take sourceFolder into account.
*
* @param element
* a named element.
* @return the file name of the named element
*/
@Override
public String getFileName(NamedElement element) {
if (sourceFolder == null) {
sourceFolder = GenUtils.getSourceFolder(element);
}
return sourceFolder + locStrategy.getFileName(element);
}
protected void generateFile(String fileName, String content) {
if (formatCode) {
// make sure, the file exists
if (fileSystemAccess instanceof IFileExists) {
if (!((IFileExists) fileSystemAccess).existsFile(fileName)) {
fileSystemAccess.generateFile(fileName, StringConstants.EMPTY);
}
} else {
fileSystemAccess.generateFile(fileName, StringConstants.EMPTY);
}
// now write formatted content
fileSystemAccess.generateFile(fileName, format(fileName, content));
} else {
fileSystemAccess.generateFile(fileName, content);
}
}
/**
* Apply the user's currently selected formatting options to the input content.
* Return the input String in case of error.
*/
protected String format(String fileName, String content) {
// do nothing if the CDT plugin is not loaded
if (Platform.getBundle(CCorePlugin.PLUGIN_ID) == null) {
return content;
}
Map<String, Object> options = new HashMap<String, Object>();
// copy default options
options.putAll(CCorePlugin.getOptions());
if (project != null) {
ProjectScope scope = new ProjectScope(project);
IEclipsePreferences ref = scope.getNode(CCorePlugin.PLUGIN_ID);
for (String key : options.keySet()) {
if (key.startsWith(CCorePlugin.PLUGIN_ID + ".formatter")) { //$NON-NLS-1$
String value = ref.get(key, StringConstants.EMPTY);
if (value != null && value.length() > 0) {
options.put(key, value);
}
}
}
}
CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(options);
IDocument doc = new Document(content);
TextEdit edit = codeFormatter.format(CodeFormatter.K_TRANSLATION_UNIT, doc.get(), 0, doc.get().length(), 0,
null);
if (edit == null) {
Activator.log.debug("Cannot format content"); //$NON-NLS-1$
return content;
}
try {
edit.apply(doc);
return doc.get();
} catch (MalformedTreeException e) {
Activator.log.error(e);
} catch (BadLocationException e) {
Activator.log.error(e);
}
return content;
}
/**
* Apply the user's currently selected formatting options to the input content.
* Return the input String in case of error.
*/
protected String format(String content) {
// do nothing if the CDT plugin is not loaded
if (Platform.getBundle(CCorePlugin.PLUGIN_ID) == null)
return content;
CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(null);
IDocument doc = new Document(content);
TextEdit edit = codeFormatter.format(CodeFormatter.K_TRANSLATION_UNIT, doc.get(), 0, doc.get().length(), 0,
null);
if (edit == null) {
Activator.log.debug(Messages.CModelElementsCreator_CanNotFormatContent);
return content;
}
try {
edit.apply(doc);
return doc.get();
} catch (MalformedTreeException e) {
Activator.log.error(e);
} catch (BadLocationException e) {
Activator.log.error(e);
}
return content;
}
protected void createPackageFiles(IContainer packageContainer, IProgressMonitor monitor, Package pkg)
throws CoreException {
// Creates the header for the package.
// String fileContent = commentHeader +
// AcceleoDriver.evaluateURI(CppPackageHeader, pkg);
// createFile(packageContainer, "Pkg_" + pkg.getName() + "." + hppExt,
// fileContent, true);
}
protected boolean isRoot(Namespace ns) {
return GenUtils.hasStereotype(ns, CppRoot.class);
}
protected boolean noCodeGen(Element element) {
return GenUtils.hasStereotype(element, NoCodeGen.class);
}
}