| /******************************************************************************* |
| * 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; |
| } |
| } |