Exercise 5. Model Exporter

What This Exercise Is About

Creating a model exporter using the Converter API is a fairly simple task. In this exercise you will create one that generates a text file that enumerates the elements of an EMF model.

For your reference, here again is the UML class diagram describing the purchase order model:

Extended PO model


What You Should Be Able To Do

At the end of the lab, you should be able to:

Required Materials

General Advice / Warnings

Exercise Instructions

This exercise is carried out entirely using the Eclipse Software Development Kit (SDK) version 3.2 with the Eclipse Modeling Framework (EMF) 2.2 installed into it. The exercise instructions refer to this product as either Eclipse or as "the workbench".

These instructions are located in the exercises/Exercise5_Model_Exporter folder. If you browse into this location by expanding the folders, you should find one additional folder: org.eclipse.emf.tutorial.advanced.po. This is an Eclipse project that you will import into your workspace to complete this exercise.


Directions

Step A: Verify workbench configuration

  1. See Exercise 1, Step A.

Step B: Import the project into the workspace

  1. Import the project org.example.emf.exporter.text using the instructions available at Exercise 1, Step B. Do NOT import the org.eclipse.emf.tutorial.advanced.po project
  2. The org.example.emf.exporter.text now available in the workspace could be considered the skelleton of any model exporter. Take some time now and look at the plugin.xml file, in which the wizard you will create during this exercise is registered using the org.eclipse.emf.exporter.modelExporterDescriptors extension point.


Step C: Create the TextExporter class

  1. Right-click the org.example.emf.exporter.text package located in the src folder of the org.example.emf.exporter.text project, and select New -> Class
  2. Create a class named TextExporter that extends org.eclipse.emf.exporter.ModelExporter. Make sure the Inherited abstract methods option is selected.
  3. New class: TextExporter

  4. Change the method getID() to return the Text Exporter ID, as indicated below:
    public String getID()
    {
      return "org.example.emf.exporter.text";
    }
  5. Rigth-click the TextExporter.java in the Package Explorer and select Source -> Override/Implement methods...
  6. Select the methods doCheckEPackageArtifactLocation(String, String), doExport(Monitor, ExportData), and getDefaultArtifactLocation(EPackage) and press the OK button.
  7. TextExporter: Override Methods

  8. Change the method getDefaultArtifactLocation(EPackage) to ensure that txt is the file extension of the files that will be created by the exporter.
    protected String getDefaultArtifactLocation(EPackage ePackage)
    {
      return getDefaultArtifactFileName(ePackage) + ".txt";
    }
  9. Change the method doCheckEPackageArtifactLocation(String, String) to return a warning message if the user uses a file extension other than txt.
    protected String doCheckEPackageArtifactLocation(String location, String packageName)
    {
      if (!location.endsWith(".txt"))
      {
        return TextExporterPlugin.INSTANCE.getString("_UI_InvalidArtifactFileNameExtension_message");
      }
      return super.doCheckEPackageArtifactLocation(location, packageName);
    }
  10. Change the method doExport(Monitor, ExportData) to log a message in the console output.
    protected void doExport(Monitor monitor, ExportData exportData) throws Exception
    {
      System.out.println("TextExporter.doExport()");
    }
    
  11. Note: Eclipse provides a very handy way of doing this: type syst, invoke the content assitent using Ctrl+Space, and select the systrace - print the current method to standard out editor template.

  12. Use the Ctrl+Shift+O shortcut to add any missing import statements.

Step D: Create the TextExporterWizard class

  1. Right-click the org.example.emf.exporter.text.ui package located in the src folder of the org.example.emf.exporter.text project, and select New -> Class
  2. Create a class named TextExporterWizard that extends org.eclipse.emf.exporter.ui.contribution.base.ModelExporterWizard. Make sure the Inherited abstract methods option is selected.
  3. New Class: TextExporterWizard

  4. Change the method createModelConverter() to return the Text Exporter created in step C.
    protected ModelConverter createModelConverter()
    {
      return new TextExporter();
    }
  5. Add a addPages() method that creates the pages of the Text Exporter wizard.
    public void addPages()
    {
      ModelExporterDirectoryURIPage directoryURIPage = new ModelExporterDirectoryURIPage(getModelExporter(), "TextExporterBaseLocationPage");
      directoryURIPage.setTitle(TextExporterPlugin.INSTANCE.getString("_UI_TextImport_title"));
      addPage(directoryURIPage);
      
      ModelExporterPackagePage packagePage = new ModelExporterPackagePage(getModelExporter(), "TextExporterGenModelDetailPage");
      packagePage.setTitle(TextExporterPlugin.INSTANCE.getString("_UI_TextImport_title"));
      packagePage.setShowReferencedGenModels(true);
      addPage(packagePage);
      
      ModelExporterOptionsPage optionsPage = new ModelExporterOptionsPage(getModelExporter(), "TextExporterOptionsPage");
      optionsPage.setTitle(TextExporterPlugin.INSTANCE.getString("_UI_TextImport_title"));
      addPage(optionsPage);
    }
  6. Notice that all the pages above belong to the Converter API. You didn't have to write one single line of SWT code.

  7. Use the Ctrl+Shift+O shortcut to add any missing import statements.

Step E: Test the TextExporter

All steps below will be done in a second, testing workbench, or Eclipse Application workbench.

  1. Launch a new Eclipse Application to try out your editor.
    1. Select your plug-in project org.example.emf.exporter.text in the Package Explorer.
    2. Select Run As -> Eclipse Application from the Run toolbar drop-down or menu. Note that the Run menu is context-sensitive, so options available under Run As will change depending on where your cursor is (which view/editor and what file type).
    3. Once it opens, close or minimize the Welcome view.
    4. Select About Eclipse SDK from the Help menu, click on Plug-in Details, and look for the contributed org.example.emf.exporter.text plug-in.
  2. Import the project org.eclipse.emf.tutorial.advanced.po using the instructions available at Exercise 1, Step B.
  3. In the Package Explorer, right-click the PurchaseOrder.genmodel file available in the model folder of the project imported above and select Export Model....
  4. In the Model Exporter Wizard, select the Text exporter and press the Next > button.
  5. Model Export Wizard, page 1

  6. Keep the default Directory URI and press the Next > button.
  7. Press the Select All button and then the Next > button.
  8. Model Export Wizard, page 3

  9. Select the Remember this wizard's settings and press the Finish button.
  10. Go back to the first Eclipse instance (the one with the TextExporter code) and look at the Console view (which can be opened using the Alt+Shift+Q,C shortcut). You should be reading the message generated by the doExport(Monitor, ExportData) method of the TextExporter
  11. Console View

  12. In the second, testing workbench, select File -> Exit to exit the Eclipse Application.

Step F: Implement the TextExporter

Of course the objective of the TextExporter is not to output a simple message in the console view. You will now add the code necessary to generate a textual description of each EPackage of any EMF model, and to create each exported artifact.

  1. Add toString(GenPackage) and save(URI, String) methods to the TextExporter. As you are probably guessing, the former will be responsible for generating the textual description and the latter to save it into the appropriate file.
    private String toString(GenPackage genPackage)
    {
      return null;
    }
    
    private void save(URI uri, String content) throws Exception
    {
    }
  2. Change the doExport(Monitor, ExportData) method to invoke the methods defined above. The genPackage and uri arguments can be obtained thought the ExportData object. This object is a very simple data structure defined to easily communicate the information acquired in the wizard to the exporter.
    protected void doExport(Monitor monitor, ExportData exportData) throws Exception
    {
      for (Iterator i = exportData.genPackageToArtifactURI.entrySet().iterator(); i.hasNext();)
      {
        Map.Entry entry = (Map.Entry)i.next();      
        GenPackage genPackage = (GenPackage)entry.getKey();
        URI exportedArtifactURI = (URI)entry.getValue();
        
        String content = toString(genPackage);
        save(exportedArtifactURI, content);
      }
    }
  3. If you take some time to analyze the ExportData object, you will notice that it holds the following maps:

    1. genPackageToArtifactURI which associates an exported GenPackage to the URI of the artifact to be created.
    2. genPackageToReferencedGenPackages which associates an exported GenPackage to the GenPackages it refers to.
    3. referencedGenPackagesToArtifactURI which associates a referenced GenPackage to the URI of an existing artifact created on a previous export operation.

    Paying closer attention to the "packages" page of the Exporter wizard, you will notice that the ExportData is an object representation of the information acquired there

    Visual explanation of the ExportData

  4. Change save(URI, String) to save the specified text into the file defined by the URI. One approach to implement this method would be to manually inspect the URI to decide how to create the file (a Eclipse platform URI would be created using the Eclipse resource API while a file URI would be created using the java.io facilities). A simplified approach delegates this task to a URIConverter.
    private void save(URI uri, String content) throws Exception
    {
      URIConverter uriConverter = new URIConverterImpl();
      OutputStream outputStream = uriConverter.createOutputStream(uri);
      outputStream.write(content.getBytes("UTF-8"));
      outputStream.close();
    }
  5. Although the URIConverter is being instantiated directly in the snippet above, it would be preferred to obtain a reference to it through a ResourceSet. You could, for example, change doExport(Monitor, ExportData) to cache the URIConverter of a GenPackage's ResourceSet.

    uriConverter = genPackage.eResource().getResourceSet().getURIConverter()
  6. Change toString(GenPackage) to produce the textual description of the GenPackage. The snippet below will output details about the classes, data types and enumerations.
    private String toString(GenPackage genPackage)
    {
      StringBuffer sb = new StringBuffer();
      sb.append("Package: ").append(genPackage.getQualifiedPackageName());
    
      sb.append("\n");
      EPackage ePackage = genPackage.getEcorePackage();    
      sb.append("\n").append("Ecore URI: ").append(ePackage.eResource().getURI().toString());
      sb.append("\n").append("GenModel URI: ").append(genPackage.eResource().getURI().toString());
      
      for (Iterator i = ePackage.getEClassifiers().iterator(); i.hasNext();)
      {
        sb.append("\n");
        
        EClassifier eClassifier = (EClassifier)i.next();
        if (eClassifier instanceof EClass)
        {
          EClass eClass = (EClass)eClassifier;
          sb.append("\n").append("Class: ").append(eClass.getName());
          
          if (!eClass.getEAttributes().isEmpty())
          {
            sb.append("\n\t").append("Attributes: ");
            for (Iterator j = eClass.getEAttributes().iterator(); j.hasNext();)
            {
              EAttribute eAttribute = (EAttribute)j.next();
              sb.append(eAttribute.getName());
              if (j.hasNext()) sb.append(", ");
            }
          }
        }
        else if (eClassifier instanceof EEnum)
        {
          EEnum eEnum = (EEnum)eClassifier;
          sb.append("\n").append("Enumeration: ").append(eEnum.getName());
          if (!eEnum.getELiterals().isEmpty())
          {
            sb.append("\n\t").append("Literals: ");
            for (Iterator j = eEnum.getELiterals().iterator(); j.hasNext();)
            {
              EEnumLiteral enumLiteral = (EEnumLiteral)j.next();
              sb.append(enumLiteral.getName());
              if (j.hasNext()) sb.append(", ");
            }
          }
        }
        else if (eClassifier instanceof EDataType)
        {
          EDataType eDataType = (EDataType)eClassifier;
          sb.append("\n").append("Data Type: ").append(eDataType.getName());
        }
      }
      
      return sb.toString();
    }
  7. Use the Ctrl+Shift+O shortcut to add any missing import statements.
  8. Select File -> Save to save the changes.

Step G: Execute the TextExporter

  1. Repeat the steps one to seven of the Step E.
  2. Open the exported files (customer.txt, datatype.txt and po.txt) located in the model folder of the org.eclipse.emf.tutorial.advanced.po project.
  3. Explorer with the exported files

  4. Take some time to experiment with your exporter. Change the implementation, the names of the files to be generated, etc.. Also, open the PurchaseOrder.genmodel and use the Generator -> Show Annotations menu item to view the GenAnnotations created to store the Export wizard settings.

Summary

You created a simple model exporter, which exposed you to the Converter API and to the EMF metadata which can be use to traverse any model. You also saw how to use the URIConverter to easily create the file specified by a URI.