Skip to content

Creating a Textual Editor

Stephan Seifermann edited this page Feb 8, 2018 · 22 revisions

The goal of this page is to create a textual editor for a chosen diagram type that you can integrate in the Cooperate Modeling Environment. The required steps include

  1. Definition and implementation of a grammar and a meta model
  2. Implementation of state calculators
  3. Implementation of automated issue resolutions
  4. Implementation of outline
  5. Implementation of comparator for CDO

Foundations

  • Xtext
    • Textual editor framework
    • Important topics: Grammar, quickfixes, validation, outline
  • EMF
    • Modeling framework for Eclipse
    • Important topics: Creating a meta model, generating code
  • UML 2.5
    • Official UML standard
    • Important topics depend on the chosen diagram type, you should read this on demand

Definition of a meta model

The meta model defines the structure of the information displayed in your textual syntax. When working with Xtext, the structure of your meta model and your grammar are tigthly coupled. Therefore, it might be necessary to adjust the meta model and grammar together if you want to change anything.

First of all, you have to create a new plugin project:

  • Create a plugin project that will include the meta model (it is best practice to name the project ...activity.metamodel if you create a meta model for an activity diagram)
    • Choose Eclilpse as the target platform in the wizard and do not change the default version
    • Create a new folder model
    • Add a dependency to de.cooperateproject.modeling.textual.common.metamodel

Afterwards, you can create the meta model in the model folder:

  • Set the name, ns prefix and ns uri of the root package
  • Load the textual commons resource
    • If the textual commons meta model is installed, use the registered packages and look for http://www.cooperateproject.de/modeling/textual/commons
    • If the textual commons meta model is located in your workspace, use the workspace browser and select the ecore model
  • For every non-artificial parser rule in your grammar, create a concrete EClass
  • For every feature in your grammar, create an according EStructuralFeature
  • Use the base classes of the textual commons meta model whenever possible
    • Every concrete EClass that has a counterpart in the UML meta model has to (transitively) extend the UMLReferencingElement EClass
  • Add interfaces, abstract classes, and relations to improve the structure of your meta model (e.g. to introduce reasonable inheritance structures)

Next, you have to create the generator model with the same name in the model folder:

  • Create a new EMF generator model from the wizard
    • Give it the file name as the ecore model
    • Choose Ecore model (CDO Native)
    • Select your newly created ecore model
    • Select the root package of your ecore model as root package
    • Select all other referenced models in the bottom area of the dialog page
  • Open the generator model
    • Select the root element
      • change the Feature Delegation in the group Model to Dynamic
      • change the Model Directory in the group Model from .../src to .../src-gen
    • Select the first entry under the root element
      • set the Base Package in the group All to the name of your plugin project
  • Open the generator model in a text editor
    • For all entries in the usedGenPackages attribute, replace ../../ with platform:/plugin/

Finally, you should generate the code for model, edit, and editor plugins with the generator model. You have to regenerate the code everytime you change the model.

Definition of a grammar

Before designing your grammar, please have a look at our general syntax guidelines and existing syntaxes. Additionally, you have to choose relevant elements for your diagram and omit elements that are not essential or that are rarely used.

First, you should create a new Xtext project.

  • Select Xtext Project From Existing Ecore Models in the new wizard
  • Select the generator model you created in the step before
  • Choose your entry rule (usually you want to choose the XYDiagram element)
  • Use the project name that you used for the meta model plugin but omit the .metamodel
  • Use a short file extension (usually 3 digits) such as abbreviations suggested in the UML 2.5 standard Annex A
  • Use the same name like the project name but append the diagram abbreviation
  • Keep all settings regarding plugin generation as it is
  • After pressing finish, 5 projects should have been created

In the next step, you can fix the errors in the generated grammar file:

  • Delete all existing rules and start over
  • Replace all import statements that are marked with the platform resource URI
    • old: import "http://www.cooperateproject.de/modeling/textual/cls/Cls"
    • new: import "platform:/resource/de.cooperateproject.modeling.textual.cls.metamodel/model/cls.ecore"

In order to generate code from your grammar file, you have to use the MWE2 workflow. Adjust the workflow in order to generate the code required for integration in the Cooperate Modeling Environment. You can use the template given below. Do not forget to replace every placeholder in angle brackets.

module <module qualifier, just keep it>

import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*
import de.cooperateproject.modeling.textual.xtext.generator.*

var rootPath = ".."
var basename = "<the base package you chose in the generator model when defining the meta model>"

Workflow {

    bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
        platformUri = "${rootPath}"
        scanClassPath = true

        uriMap = {
            from = "platform:/plugin/org.eclipse.emf.codegen.ecore/model/GenModel.genmodel"
            to = "platform:/resource/org.eclipse.emf.codegen.ecore/model/GenModel.genmodel"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.emf.ecore/model/Ecore.genmodel"
            to = "platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.uml2.codegen.ecore/model/GenModel.genmodel"
            to = "platform:/resource/org.eclipse.uml2.codegen.ecore/model/GenModel.genmodel"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.uml2.uml/model/UML.genmodel"
            to = "platform:/resource/org.eclipse.uml2.uml/model/UML.genmodel"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.emf.codegen.ecore/model/GenModel.ecore"
            to = "platform:/resource/org.eclipse.emf.codegen.ecore/model/GenModel.ecore"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.emf.ecore/model/Ecore.ecore"
            to = "platform:/resource/org.eclipse.emf.ecore/model/Ecore.ecore"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.uml2.codegen.ecore/model/GenModel.ecore"
            to = "platform:/resource/org.eclipse.uml2.codegen.ecore/model/GenModel.ecore"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.uml2.uml/model/UML.ecore"
            to = "platform:/resource/org.eclipse.uml2.uml/model/UML.ecore"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.uml2.types/model/Types.genmodel"
            to = "platform:/resource/org.eclipse.uml2.types/model/Types.genmodel"
        }
        uriMap = {
            from = "platform:/plugin/org.eclipse.uml2.types/model/Types.ecore"
            to = "platform:/resource/org.eclipse.uml2.types/model/Types.ecore"
        }

        registerGeneratedEPackage = "org.eclipse.uml2.codegen.ecore.genmodel.GenModelPackage"
        registerGeneratedEPackage = "org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage"
    }
    
    component = CooperateXtextGenerator {       
        configuration = {
            project = StandardProjectConfig {
                baseName = "${basename}"
                rootPath = rootPath
                runtimeTest = {
                    enabled = true
                    root = "../../tests/${basename}.tests"
                }
                eclipsePlugin = {
                    enabled = true
                }
                eclipsePluginTest = {
                    enabled = true
                    root = "../../tests/${basename}.ui.tests"
                }
                createEclipseMetaData = true
            }
            code = {
                encoding = "UTF-8"
                fileHeader = "/*\n * generated by Xtext \${version}\n */"
            }
            
        }
        
        language = StandardLanguage {
            name = "${basename}.<file extension starting with an upper case character, keep it>"
            fileExtensions = "<file extension, keep it>"
            
            referencedResource = "platform:/resource/${basename}.metamodel/model/<name of your genmodel file>.genmodel"
            referencedResource = "platform:/resource/${basename}.metamodel/model/<name of your meta model file>.ecore"
            referencedResource = "platform:/plugin/org.eclipse.uml2.uml/model/UML.ecore"
            referencedResource = "platform:/plugin/org.eclipse.uml2.uml/model/UML.genmodel"
            
            renameRefactoring = fragments.CooperateRenameGeneratorFragment2 auto-inject {}
            
            fragment = ecore2xtext.Ecore2XtextValueConverterServiceFragment2 auto-inject {}

            fragment = org.eclipse.xtext.generator.adapter.FragmentAdapter {
                fragment = org.eclipse.xtext.generator.formatting2.Formatter2Fragment {}
            }
            
            fragment = scoping.ImportNamespacesScopingFragment2 auto-inject {}
            fragment = exporting.QualifiedNamesFragment2 auto-inject {}

            quickFixProvider = fragments.CooperateQuickfixProviderFragment2 auto-inject {}

            generator = {
                generateStub = false
            }

            serializer = {
                generateStub = false
            }
            
            validator = fragments.CooperateValidatorFragment2 auto-inject {
                generateXtendStub = false
            }
                
            fragment = junit.Junit4Fragment2 {
                generateStub = false
                generateXtendStub = false
            }
                                
            fragment = fragments.CooperateCDOXtextFragment2 auto-inject {}
        }
    }
}

After editing the MWE2 workflow, you can execute it. This should lead to 4 additional projects. The workflow tries to adjust several files including the plugin.xml file every time you run the workflow. If the workflow is not able to merge the changes, there will be a plugin.xml_gen file. If this file exists, you have to merge the changes manually. Otherwise, the changes will not be applied.

After generating the plugins required for the textual editor, you have to adjust some parts of the generated code:

  • <basename>.ui plugin - plugin.xml
    • Add the matching strategy de.cooperateproject.modeling.textual.xtext.runtime.ui.editor.CooperateEditorMatchingStrategy to the editor extension in the extension point org.eclipse.ui.editors
    • Add the category de.cooperateproject.ui.preferences.cooperate to the first page of the page extension in the extension point org.eclipse.ui.preferencePages
    • Add the term Diagram to the name of the first page of the page extension in the extension point org.eclipse.ui.preferencePages

Afer these changes, you can complete your grammar (and meta model). You can always test the editor after executing the MWE2 workflow, starting a new Eclipse instance, and creating an empty file ending with the file extension you specified.

Implementation of State Calculators

Clone this wiki locally