blob: bb10fcac18d8d149585422e0fd61aac32d471e0f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency
* 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:
* Pierre Allard,
* Regent L'Archeveque - initial API and implementation
*
* SPDX-License-Identifier: EPL-1.0
*******************************************************************************/
package org.eclipse.apogy.common.emf.codegen;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Iterator;
import org.eclipse.apogy.common.ApogyCommonPackage;
import org.eclipse.apogy.common.emf.ApogyCommonEMFFacade;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.codegen.ecore.generator.GeneratorAdapterFactory;
import org.eclipse.emf.codegen.ecore.genmodel.GenClass;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.codegen.ecore.genmodel.generator.GenBaseGeneratorAdapter;
import org.eclipse.emf.codegen.ecore.genmodel.generator.GenModelGeneratorAdapter;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.TextEdit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*
* This class supports usage of the @Apogy annotations:
* 1) hasCustomClass="<boolean>"
* 2) hasCustomProvider="<boolean>"
*
* Apply the changes to the classes that extends custom classes:
* 1) class %sImpl extends %sImpl -> abstract class %sImpl extends %sCustomImpl
* 2) class %sItemProvider extends %sItemProvider -> abstract class %sItemProvider extends %sCustomItemProvider
*
* The classes adapts the EMF generated model factory and EMF generated {ItemProviderAdapterFactory}
* override create%() and create%sAdapter() methods accordingly.
*
* The also ensures the generated EMF model implementation classes extends the super custom implementation
* class if the super class has one of the custom class or item provider @Apogy annotation.
*
* The @Apogy(isSingleton="<boolean>") is handled by the injected JET template:
* platform://plugin/org.eclipse.apogy.common.emf.codegen/templates/model/Class/insert.javajetinc
*/
public class ApogyGenModelGeneratorAdapter extends GenModelGeneratorAdapter {
// Refers the Logger.
private static final Logger Logger = LoggerFactory.getLogger(ApogyGenModelGeneratorAdapter.class);
/*
* Default constructor required by the extension point
* 'point="org.eclipse.emf.codegen.ecore.generatorAdapters.adapter">'
*/
public ApogyGenModelGeneratorAdapter() {
super(null);
}
/*
* Constructor invoked by the default EMF Code generator.
*
* @param generatorAdapterFactory Reference to the EMF generator factory.
*/
public ApogyGenModelGeneratorAdapter(GeneratorAdapterFactory generatorAdapterFactory) {
super(generatorAdapterFactory);
}
@Override
protected Diagnostic doPostGenerate(Object object, Object projectType) {
// Adapt model and edit only.
if (projectType.equals(MODEL_PROJECT_TYPE) || projectType.equals(EDIT_PROJECT_TYPE)) {
String type = ((String) projectType).substring(((String) projectType).lastIndexOf(".") + 1,
((String) projectType).length());
// Get the GenModel.
GenModel genModel = ((GenModel) this.generatingObject);
Logger.debug("Updating(" + type + ") GenModel: " + genModel.getQualifiedModelModuleName() + " started");
try {
// Process all EPackages.
for (GenPackage genPackage : genModel.getGenPackages()) {
Logger.debug("\tUpdating(" + type + ") GenPackage: " + genPackage.getQualifiedPackageName()
+ " started");
/*
* Process all EClasses.
*/
for (GenClass genClass : genPackage.getGenClasses()) {
EClass eClass = genClass.getEcoreClass();
Logger.debug("\t\t<" + eClass.getName() + "> <"
+ (projectType.equals(MODEL_PROJECT_TYPE) ? "Model" : "Edit") + "> started");
// Check the presence of a super class.
EClass superEClass = eClass.getESuperTypes().isEmpty() ? null : eClass.getESuperTypes().get(0);
Logger.debug("\t\t\t<" + eClass.getName() + "> extends <"
+ (superEClass == null ? null : superEClass.getName()) + ">");
/*
* If we process the model project.
*/
if (projectType.equals(GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE)
&& !genClass.getEcoreClass().isInterface()) {
applyCustomClassFixes(genClass, superEClass, genClass.getGenModel().getModelDirectory(),
genClass.getGenPackage().getClassPackageName(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__HAS_CUSTOM_CLASS_KEY
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__DEFAULT_CLASS_SUFFIX
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__CUSTOM_CLASS_SUFFIX
.getDefaultValueLiteral());
Logger.debug("\t\t\t<" + eClass.getName() + "> extends Custom <"
+ (superEClass == null ? "null" : superEClass.getName()) + ">");
}
/*
* If we process the edit project.
*/
if (projectType.equals(GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE) && genModel.hasEditSupport()
&& !genClass.getEcoreClass().isInterface()) {
applyCustomClassFixes(genClass, superEClass, genClass.getGenModel().getEditDirectory(),
genClass.getGenPackage().getProviderPackageName(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__HAS_CUSTOM_ITEM_PROVIDER_KEY
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__DEFAULT_ITEM_PROVIDER_CLASS_SUFFIX
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__CUSTOM_ITEM_PROVIDER_CLASS_SUFFIX
.getDefaultValueLiteral());
Logger.debug("\t\t\t<" + eClass.getName() + "ItemProvider> extends Custom<"
+ (superEClass == null ? "null" : superEClass.getName()) + "ItemProvider"
+ "ItemProvider>");
}
Logger.debug("\t\t<" + eClass.getName() + "> <"
+ (projectType.equals(MODEL_PROJECT_TYPE) ? "Model" : "Edit") + "> done");
}
/*
* Process Model Factory.
*/
if (projectType.equals(GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE)) {
Logger.debug("\t\tUpdating(" + type + ") GenPackage: " + genPackage.getQualifiedPackageName()
+ " Factory started");
applyCustomFactoryFixes(genPackage, genPackage.getGenModel().getModelDirectory(),
genPackage.getImportedFactoryClassName(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__HAS_CUSTOM_CLASS_KEY
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__DEFAULT_CLASS_SUFFIX
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__CUSTOM_CLASS_SUFFIX
.getDefaultValueLiteral());
Logger.debug("\t\tUpdating(" + type + ") GenPackage: " + genPackage.getQualifiedPackageName()
+ " Factory done");
}
/*
* Process Edit Factory (Item Provider).
*/
if (genModel.hasEditSupport() && projectType.equals(GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE)) {
Logger.debug("\t\tUpdating(" + type + ") GenPackage: " + genPackage.getQualifiedPackageName()
+ " Item Provider Adapter Factory started");
applyCustomFactoryFixes(genPackage, genPackage.getGenModel().getEditDirectory(),
genPackage.getImportedItemProviderAdapterFactoryClassName(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__HAS_CUSTOM_ITEM_PROVIDER_KEY
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__DEFAULT_ITEM_PROVIDER_CLASS_SUFFIX
.getDefaultValueLiteral(),
ApogyCommonPackage.Literals.APOGY_GEN_CLASS__CUSTOM_ITEM_PROVIDER_CLASS_SUFFIX
.getDefaultValueLiteral());
Logger.debug("\t\tUpdating(" + type + ") GenPackage: " + genPackage.getQualifiedPackageName()
+ " Item Provider Adapter Factory done");
}
Logger.debug("\tUpdating(" + type + ") package: " + genPackage.getQualifiedPackageName() + " done");
}
} catch (Throwable t) {
Logger.error("Apogy code generator had problems to update the model "
+ genModel.getQualifiedModelModuleName(), t);
}
Logger.debug("Updating(" + type + ") model: " + genModel.getQualifiedModelModuleName() + " done");
}
return Diagnostic.OK_INSTANCE;
}
/*
* Apply the changes to the classes that extends custom classes. 1) class %sImpl
* extends %sImpl -> abstract class %sImpl extends %sCustomImpl 2) class
* %sItemProvider extends %sItemProvider -> abstract class %sItemProvider
* extends %sCustomItemProvider
*
* @param genClass Generator class that refers the class that may extend a
* custom super class.
*
* @param superEClass Super class of the class being processed.
*
* @param bundleDirectory Bundle directory that refers to the model or edit
* directory.
*
* @param classPackageName Package name where the implementations classes are
* stored (e.g. "impl", "provider").
*
* @param defaultClassSuffix Suffix value of the generated implementation
* classes (e.g. "Impl", "ItemProvider").
*
* @param customClassSuffix Suffix value of the custom implementation classes
* (e.g. "CustomImpl", "CustomItemProvider").
*/
private void applyCustomClassFixes(GenClass genClass, EClass superEClass, String bundleDirectory,
String classPackageName, String hasCustomCodeKey, String defaultClassSuffix, String customClassSuffix) {
// Get the Generated Item Provider URI.
URI javaClassURI = toURI(bundleDirectory).appendSegments(classPackageName.split("\\."))
.appendSegment(genClass.getName() + defaultClassSuffix).appendFileExtension("java");
// Apply changes to the file.
applyChanges(javaClassURI, new ASTVisitor() {
@SuppressWarnings("unchecked")
@Override
public boolean visit(TypeDeclaration node) {
// Set abstract modifiers to generated class if required.
EAnnotation eAnnotation = ApogyCommonEMFFacade.INSTANCE.getApogyEAnnotation(genClass.getEcoreClass());
if (ApogyCommonEMFFacade.INSTANCE.isTrue(eAnnotation, hasCustomCodeKey)) {
if ((node.getModifiers() & ModifierKeyword.ABSTRACT_KEYWORD.toFlagValue()) == 0) {
node.modifiers().add(node.getAST().newModifier(ModifierKeyword.ABSTRACT_KEYWORD));
Logger.debug("\t\t\t\tabstract modifiers turned on.");
}
}
// Check if the node super type is a custom class.
EAnnotation superClassEAnnotation = ApogyCommonEMFFacade.INSTANCE.getApogyEAnnotation(superEClass);
if (ApogyCommonEMFFacade.INSTANCE.isTrue(superClassEAnnotation, hasCustomCodeKey)) {
Type nodeType = node.getSuperclassType();
// Considered normal and parameterized types.
SimpleType simpleType = null;
if (nodeType instanceof SimpleType) {
simpleType = (SimpleType) nodeType;
} else if (nodeType instanceof ParameterizedType) {
simpleType = (SimpleType) ((ParameterizedType) nodeType).getType();
}
if (simpleType != null) {
SimpleName nodeSuperClassName = (SimpleName) simpleType.getName();
String oldName = nodeSuperClassName.getFullyQualifiedName();
if ((superEClass.getName() + defaultClassSuffix).equals(oldName)) {
// Default generated class extends the custom super class.
String newName = oldName.replaceAll(defaultClassSuffix + "$", customClassSuffix);
simpleType.setName(node.getAST().newName(newName));
// Fix Import to use the custom super class.
CompilationUnit compilationUnit = getCompilationUnit(node);
Iterator<ImportDeclaration> imports = compilationUnit.imports().iterator();
boolean found = false;
while (!found && imports.hasNext()) {
ImportDeclaration currentImportDeclaration = imports.next();
Name importName = currentImportDeclaration.getName();
String fullyQualifiedName = importName.getFullyQualifiedName();
if (fullyQualifiedName.endsWith(superEClass.getName() + defaultClassSuffix)) {
currentImportDeclaration.setName(node.getAST().newName(
fullyQualifiedName.replaceAll(defaultClassSuffix, customClassSuffix)));
found = true;
}
}
}
}
}
return super.visit(node);
}
});
}
/*
* Apply the changes to the model and edit factories to instance custom classes
* if required. 1) class %sImpl extends %sImpl -> class %sImpl extends
* %sCustomImpl
*
* 2) class %sItemProvider extends %sItemProvider -> class %sItemProvider
* extends %sCustomItemProvider
*
* @param genPackage Generator package that may contain custom classes.
*
* @param bundleDirectory Bundle directory that refers to the model or edit
* directory.
*
* @param factoryClassName Package name where the implementations classes are
* stored (e.g. "%sFactoryImpl", "%sItemProviderAdapterFactory").
*
* @param defaultClassSuffix Suffix value of the generated implementation
* classes (e.g. "Impl", "ItemProvider").
*
* @param customClassSuffix Suffix value of the custom implementation classes
* (e.g. "CustomImpl", "CustomItemProvider").
*/
private void applyCustomFactoryFixes(GenPackage genPackage, String bundleDirectory, String factoryClassName,
String hasCustomKey, String defaultClassSuffix, String customClassSuffix) {
// Get the Generated Model Factory URI.
URI javaFactoryURI = toURI(bundleDirectory).appendSegments(factoryClassName.split("\\."))
.appendFileExtension("java");
applyChanges(javaFactoryURI, new ASTVisitor() {
@Override
public boolean visit(ClassInstanceCreation node) {
Type nodeType = node.getType();
SimpleType simpleType = null;
if (nodeType instanceof SimpleType) {
simpleType = (SimpleType) nodeType;
} else if (nodeType instanceof ParameterizedType) {
simpleType = (SimpleType) ((ParameterizedType) nodeType).getType();
}
if (simpleType != null) {
Name name = simpleType.getName();
EClassifier eClassifier = genPackage.getEcorePackage()
.getEClassifier(name.getFullyQualifiedName().replaceAll(defaultClassSuffix + "$", ""));
if (eClassifier != null && eClassifier instanceof EClass) {
EClass eClass = (EClass) eClassifier;
EAnnotation eAnnotation = ApogyCommonEMFFacade.INSTANCE.getApogyEAnnotation(eClass);
if (ApogyCommonEMFFacade.INSTANCE.isTrue(eAnnotation, hasCustomKey)) {
simpleType.setName(node.getAST().newName(name.getFullyQualifiedName()
.replaceAll(defaultClassSuffix + "$", customClassSuffix)));
}
}
}
return super.visit(node);
}
});
}
/*
* Applies changes to the file specified.
*
* @param uri Refers the file being update.
*
* @param astVisiton Refers the visitor that makes the updates.
*/
protected void applyChanges(URI uri, ASTVisitor astVisitor) {
try {
// Read the content.
InputStream inputStream = getURIConverter().createInputStream(uri);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
byte[] data = new byte[bufferedInputStream.available()];
bufferedInputStream.read(data);
bufferedInputStream.close();
String content = new String(data);
// Parse the content.
ASTParser parser = ASTParser.newParser(AST.JLS10);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setSource(content.toCharArray());
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
astRoot.recordModifications();
// Apply AST changes.
astRoot.accept(astVisitor);
// Apply changes to the AST document.
Document document = new Document(content);
TextEdit edits = astRoot.rewrite(document, null);
edits.apply(document);
// Saves the changes on disk.
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IFile file = root.getFile(new Path(uri.toString()));
file.setContents(new ByteArrayInputStream(document.get().getBytes()), true, false, null);
} catch (Exception e) {
Logger.error("The apogy generator adapter is unabled to update : " + uri, e);
}
}
/*
* Returns the CompilationUnit that contains the specified ASTNode.
*
* @param node Reference to the ASTNode.
*
* @return Reference The root Compilation Unit or null if not available.
*/
private static CompilationUnit getCompilationUnit(ASTNode node) {
ASTNode currentNode = node;
if (currentNode != null) {
while (currentNode.getParent() != null) {
currentNode = currentNode.getParent();
}
}
return (CompilationUnit) currentNode;
}
}