Showing posts with label emf. Show all posts
Showing posts with label emf. Show all posts

Tuesday, July 29, 2008

ODA Ecore Getting Started Guide -- Part Kettő

Note: this post is a continuation of The ODA-Ecore Getting Started Guide and assumes that you are familiar with its contents and the examples presented therein.

Creating a Master-Details Report


Suppose we want to report on all the Writers and their Books in a model.  Each Writer's name should appear in the Master part of the report.  Each of the Writer's Books should appear as a Detail and should display the Title, Number of Pages and the Category for the Book.

There are at least two solutions with the current implementation of the enablement plug-in.

EReference Resolution

The ODA-Ecore enablement plug-in supports EReference resolution. For any EObject that contains a reference to another EObject, it is possible to display the value of a particular EAttribute of that EReference (or an EAttribute of an EReference to an EReference, etc.). It is also possible to display the value of the full EReference itself (which is just a toString() of the EObject that is referenced).  A user selects EAttributes of EReferences when creating a new Data Set in the Data Set Columns Wizard Page.  Collections of References are returned as a <cr>-delimited String of the selected EAttributes for an EReference. The more deeply nested the EAttribute or EReference, the more dificult interpretation of the results becomes, which is why this method is prefered only in simple cases.



In this selection, we have chosen to output the publicationDate of any books written by a particular Writer, along with the address of any borrowers of the books written by the Writer.

Using EReferences is a simple solution, but it is limited in the general case. For such a simple use case, it will satisfy our needs, however.
From the Query Wizard Page, select Writer as the Invariant.
As your Boolean Query, use "self.oclIsKindOf(Writer)".
From the Columns Wizard Page, select

  • Writer::name
  • Writer::books::title
  • Writer::books::pages
  • Writer::books::category

In the BIRT report, we will insert a new table with 1 columns and 2 details. The first details row will be the Master part, the second will be the Details part.
In column 1 of the first detail row, we will drag Writer::name from the Data Set.
In the second detail row, we will insert a new table with 2 columns and 1 detail. The first column is for spacing, the second will contain the EAttribute values (for the Details part).
In the detail row of the second column of the nested table, we will insert another table with 3 columns and 1 detail row.
Finally, into these 3 columns, we will drag Writer::books::title, Writer::books::pages and Writer::books::category from the Data Set.
We could remove the header information and re-space the columns to get the layout of the information that we want.


The Layout of the Report using EReference Resolution for Master-Details display.




We have achieved our goal using three nested tables and a single query that resolves EReferences. 

Joining ResultSets

Parameterizing the BooleanOCLQuery and and using multiple joined data sets are solutions for more complex situations.

each ? in the query is replaced with the input parameters in order (the first ? is replaced with the first parameter, the second ? is replaced with the second parameter, etc.). Named parameters are not yet supported.

For the example stated above (a Master-Details table of each Writer's name as the Master with information about all the Writer's Books as the Detail.)
1. Create a DataSource and point it to the Extlibrary file.
2. Create a DataSet called "Writers Data Set" with these features:

  • Invariant -- Writer
  • Query -- self.oclIsKindOf(Writer)
  • Selected Column -- Writer::name

3. Create a DataSet called "Books Data Set" with these features:

  • Invariant -- Book
  • Query -- self.oclIsKindOf(Book) and self.oclAsType(Book).author.name = '?'
  • Selected Columns -- Book::title, Book::pages, Book::category, Book::author::name (just to prove out what we are doing)
  • Parameters -- input param1 with default value 'James Fenimore Cooper' (make sure you use quotes or you will get a Javascript error).

4. Drag the Writers Data Set into the Report and name the Table "Writers List". This is the Master part.
5. Add a column to the right of the Writer::name column.
6. Drag the Books Data Set into the empty column. This is the Details part.
7. Select the Books Data Set Table and add a Data Set Parameter Binding from Writer::name to param1.

8. Select Preview and you should now see a Master-Details report that lists the Writers' names in the first column and, for each Writer, a list of the Books associated to that Writer in a nested table in the right column.



Sorting Results


Suppose we need to sort or filter the results of a query. As only Boolean OCL queries are supported by the enablement plug-in, the driver does not currently support a SORT BY feature in the OCL query itself. Fortunately, BIRT's sorting and filtering mechanism for groups should satisfy many of our needs.
Suppose we want to find all the Writers in the object graph and print their names. In the Wizard to form the DataSet, we would choose "Writer" from the dropdown, our query would be "self.oclIsKindOf(Writer)", and we would choose to report on the column "Writer::name".
For the report, we can drag the Data Set from the Data Explorer to the report.


The unsorted preview of this report.

In the Detail Row, instead of using "[Writer::name]", we will delete the Detail row, right click and select "Insert Group". On the Group Details wizard, we will select the item to group on (here, "Writer::name") and the type of sorting we want to perform (here, "Writer::name" in ascending order).


The configuration of filters and sorters.

The resulting report should consistently contain sorted, unique results.


Duplicates are removed and writers' names appear alphabetically.

Saturday, June 28, 2008

Edit EMF Tree Nodes Inline

The problem to solve: to edit a specific property inline in a tree editor generated by EMF.

For this example, we will be using a tree editor for UML. For any UML NamedElement, we would like to be able to edit the name directly in the tree editor.

1. We will generate the UML.genmodel using the UML.ecore file from eclipse cvs

  • host: dev.eclipse.org
  • repository path: /cvsroot/modeling
  • location: org.eclipse.mdt/org.eclipse.uml2/plugins/org.eclipse.uml2.uml/model/UML.ecore

With the UML.genmodel file, emit the UML Model, Edit and Editor code. This custom UML editor is only used for this example. You may very well have a different model editor in mind for customization.


2. We have the UML model code (the domain objects and the factories to create those domain objects), UML edit code (the Item Providers and Item Provider adapter factory for the domain objects) and UML editor in three separate plug-ins. We can call these uml.inline.editing.example (for the model), uml.inline.editing.example.edit (for the item providers) and uml.inline.editing.example.editor (for the editor).

3. We want to make sure that the NamedElementItemProvider implements org.eclipse.emf.edit.provider.IUpdateableItemText because we want to be able to update the name property of NamedElements inline. The two methods we would like to include look like this:

  public void setText(Object o, String s) {
    if (!(o instanceof NamedElement)) {
      return;
    }
    NamedElement namedElement = (NamedElement) o;
    if ("".equals(s) && !namedElement.isSetName()) {
      return;
    }
    IEditorPart activeEditor = PlatformUI.getWorkbench()
      .getActiveWorkbenchWindow().getActivePage().getActiveEditor();
    if (activeEditor instanceof IEditingDomainProvider) {
      EditingDomain editingDomain = ((IEditingDomainProvider) activeEditor)
        .getEditingDomain();
      editingDomain.getCommandStack().execute(
        SetCommand.create(editingDomain, namedElement,
        UmlPackage.eINSTANCE.getNamedElement_Name(), s));
    } else {
      namedElement.setName(s);
    }  
  }
  
  @Override
  public String getUpdateableText(Object o) {
    if (o instanceof NamedElement) {
      NamedElement namedElement = (NamedElement) o;
      if (namedElement.getName() == null) {
        return "";
      }
      return namedElement.getName();
    }
    return super.getUpdateableText(o);
  }

 Keep in mind that the superclass org.eclipse.emf.edit.provider.ItemProviderAdapter already includes a method #getUpdateableText(Object).
 
4. We need to make sure the UMLItemProviderAdapterFactory will adapt NamedElements to this IUpdateableText item provider. For this, we will need to add IUpdateableText.class to the supported types in the constructor.  The generated code is in grey: 

  public UmlItemProviderAdapterFactory() {
    supportedTypes.add(IUpdateableItemText.class);
    supportedTypes.add(IEditingDomainItemProvider.class);
    supportedTypes.add(IStructuredItemContentProvider.class);
    supportedTypes.add(ITreeItemContentProvider.class);
    supportedTypes.add(IItemLabelProvider.class);
    supportedTypes.add(IItemPropertySource.class);
  }

 

What if we want to set the focus of the UMLEditor on the updateable field as soon as the NamedElement is created?

By default, if we were to hit SPACE when a NamedElement node is selected, the cell editor would gain focus.  Suppose we want the cell editor to have focus automatically whenever a new NamedElement is created.

For this we can modify the CommandStackListener in the generated UMLEditor to focus the cell editor in the tree whenever a new child or sibling NamedElement is created in the tree editor. Generated code is in grey:

  commandStack.addCommandStackListener(new CommandStackListener() {
    public void commandStackChanged(final EventObject event) {
      getContainer().getDisplay().asyncExec(new Runnable() {
        public void run() {
          firePropertyChange(IEditorPart.PROP_DIRTY);
   
          // Try to select the affected objects.
          //
          Command mostRecentCommand = ((CommandStack) event
            .getSource()).getMostRecentCommand();
          if (mostRecentCommand != null) {
            setSelectionToViewer(mostRecentCommand
              .getAffectedObjects());

            // set the focus on the editable tree cell
            if (currentViewer instanceof TreeViewer
              && (mostRecentCommand instanceof CreateChildCommand)) {
              Tree tree = ((TreeViewer) currentViewer)
                .getTree();
              TreeItem[] treeItems = tree.getSelection();
              if (treeItems.length == 1) {
                Event enableEditField = new Event();
                enableEditField.button = 1;
                enableEditField.x = treeItems[0].getBounds().x;
                enableEditField.y = treeItems[0].getBounds().y;
                tree.notifyListeners(SWT.Selection, enableEditField);
                tree.notifyListeners(SWT.MouseDown, enableEditField);
                tree.notifyListeners(SWT.MouseUp, enableEditField);
              }
            }
          }
          if (propertySheetPage != null
            && !propertySheetPage.getControl().isDisposed()) {
            propertySheetPage.refresh();
          }
        }
      });
    }
  });

It is just that easy to specify a default property for direct inline editing in a generated EMF editor!



The cell editor is focused.

The name cell editor is focused.


The tree node label is focused.

The label is focused.

Saturday, May 31, 2008

ODA Ecore Driver Backport

This post is a continuation of the description of the ODA Ecore Enablement plug-ins, now available for download from the Eclipse datatools incubator.
Recently, the ODA Ecore plug-ins were backported for JDK 1.4 and Eclipse 3.2 compatibility. This project has been moved into the datatools incubator attic directory. The same functionality is there in both sets of plug-ins, but there is no guarantee of future support for the archived version.
The primary difference between the Europa version and the backport is the backport's use of the now-deprecated org.eclipse.emf.ocl API for running ocl queries that has since been replaced by a new org.eclipse.ocl query parser. The backport does work in Europa, but the newer, EMF 2.3-dependent version of the plug-in allows shorter, context-free ocl query syntax.

Set up the ODA Ecore Driver in Eclipse 3.2


Download Eclipse 3.2 Callisto and unzip it.
Download EMF 2.2 and unzip it into your Callisto directory.
Download the EMF 2.2 examples and unzip them into your Callisto directory if you want to try the example described in the ODA Ecore Getting Started Guide.
Download EMFT Validation 1.0.1 and unzip it into your Callisto directory.
Download EMFT Transaction 1.0.2 and unzip it into your Callisto directory.
Download EMFT OCL 1.0.1 and unzip it into your Callisto directory.
Download EMFT Query 1.0.1 and unzip it into your Callisto directory.

After starting this installation of Eclipse 3.2, install the following plug-ins from the Callisto discovery site:
Database Development -> Datatools Platform Enablement 0.9 and its dependencies
Charting and Reporting -> Eclipse BIRT Report Designer Framework 2.1 and its dependencies (to run the example described in the ODA Ecore Getting Started Guide).

Import The Archived ODA Ecore Driver projects from CVS.
The Host is dev.eclipse.org
The Repository path is /cvsroot/datatools
Browse to org.eclipse.datatools.incubator/attic, select
  • org.eclipse.datatools.enablement.oda.ecore
  • org.eclipse.datatools.enablement.oda.ecore.ui
and check the projects out to your workspace.

Because the plug-ins are JDK 1.4 compatible, if you have a later version of the JDK, you might want to set your Java Compiler Preference to 1.4 Compliance to eliminate any compiler warnings.
Everything should be set-up correctly, and you should now see no compiler errors.

Try the ODA Ecore driver extlibrary Example



We can now launch a new Eclipse runtime and set up an initial BIRT report and .extlibrary file in our workspace as described in the example from the ODA Ecore Getting Started Guide.
Data source creation is the same, so we can follow the instructions without changes.
Data Set creation is slightly different because the query syntax for the backport is more strict. The boolean query in the example, self.oclIsKindOf(Writer), will work for the backport. The differences show up with more complex queries.
Suppose we want to find only references to James Fenimore Cooper. While we can use self.name = 'James Fenimore Cooper' in the latest version of the plug-in, this query will not work in the backport. The query must read self.oclIsKindOf(Writer) and self.oclAsType(Writer).name = 'James Fenimore Cooper'. The newer org.eclipse.ocl query parser can handle the stricter old-style query, but the older org.eclipse.emf.ocl parser cannot properly interpret the shorter context-free query.
Aside from that slight difference in query syntax, the plug-ins currently have the same features. So if you have the restriction that you must continue using Eclipse 3.2 or Java 1.4 and cannot upgrade to Eclipse 3.3 or EMF 2.3, but you need the features exposed by the ODA Ecore driver, you have this backport as an option!

Thursday, April 10, 2008

ODA Ecore Getting Started Guide

Overview


Now that the ODA Ecore enablement plug-ins are in the Data Tools Project incubator at Eclipse, a quick-start guide seems in order.
The Oda Ecore enablement plug-in provides a JDBC-like interface for querying object models defined with EMF. These queries are written with OCL and use the EMF Model Query OCL bridge.
The Oda Ecore UI plug-in contributes two Wizards for configuring BIRT reports: the first allows specification of a Data Source; the second provides a graphical interface for constructing queries.

Dependencies


In order to use the Oda Ecore enablement plug-in, you will need to install the following dependencies:

Datatools Connectivity
EMF Query
EMF Model Query OCL Integration
The Data Tools Platform Enablement for JDBC

In addition, to run the example, you will need to install the following plug-ins:
BIRT (for reporting query results)
EMF Examples (for the extlibrary sample)

This example has been tested so far with Ganymede M5 and M6.

Enablement Plug-ins In CVS


First, we will need to download the Oda Ecore enablement plug-ins from CVS.
File -> Import -> Projects from CVS
The Host is dev.eclipse.org
The Repository path is /cvsroot/datatools
Unless you are a committer, the User is anonymous.
Checkout from CVS at dev.eclipse.org

Browse to org.eclipse.datatools.incubator/plugins, select
org.eclipse.datatools.enablement.oda.ecore
org.eclipse.datatools.enablement.oda.ecore.ui
and check them out to your workspace.
Find the ODA Ecore enablement plug-ins in the datatools incubator.

Model File


You will also need a model file. You can create one yourself using New -> Other -> Example EMF Model Creation Wizards -> EXTLibrary Model or you can use the sample model below, which sets up two libraries with an assortment of books, some of which are in one library or both libraries, and authors, some of whom are also in one or both libraries:

my.extlibrary


<?xml version="1.0" encoding="UTF-8"?>
<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:extlib="http:///org/eclipse/emf/examples/library/extlibrary.ecore/1.0.0">
<extlib:Library address="1000 4th Ave, Seattle, WA 98104" name="Seattle Public Library">
<stock xsi:type="extlib:Book" title="David Copperfield" category="Biography" author="/0/@writers.1"/>
<stock xsi:type="extlib:Book" title="Nicholas Nickleby" category="Biography" author="/0/@writers.1"/>
<stock xsi:type="extlib:Book" title="The Deerslayer" category="Biography" author="/1/@writers.0"/>
<stock xsi:type="extlib:Book" title="The Last of the Mohicans" category="Biography"/>
<writers firstName="James" lastName="Fenimore Cooper"/>
<writers firstName="Charles" lastName="Dickens" books="/0/@stock.1 /1/@stock.3 /0/@stock.0"/>
</extlib:Library>
<extlib:Library address="308 Kirkland Ave, Kirkland, WA 98033" name="Kirkland Public Library">
<stock xsi:type="extlib:Book" title="The Last of the Mohicans" category="Biography" author="/1/@writers.0"/>
<stock xsi:type="extlib:Book" title="The Pioneers"/>
<stock xsi:type="extlib:Book" title="Around The World In 80 Days" category="ScienceFiction" author="/1/@writers.2"/>
<stock xsi:type="extlib:Book" title="The Pickwick Papers" author="/0/@writers.1"/>
<writers firstName="James Fenimore" lastName="Cooper" books="/0/@stock.2 /1/@stock.0"/>
<writers firstName="Charles" lastName="Dickens"/>
<writers address="" firstName="Jules" lastName="Verne" books="/1/@stock.2"/>
</extlib:Library>
</xmi:XMI>



my.extlibrary example file
Feel free to modify the model file with the extlibrary example editor.

Launch Eclipse


There is nothing special to do here. Just open the Run Configurations Dialog, create a new Eclipse Application runtime configuration, if you do not already have one, and hit Run.
An example Eclipse runtime configuration.

BIRT Report


In this new Eclipse runtime, create a new General project. Here we can call it hello.world.oda.ecore. Drop your .extlibrary sample file into the new project. Now create a new Blank BIRT Report and switch to the Reporting perspective. Here we use the Wizard's default name for the report. It should be automatically opened in an editor.
A new BIRT report in your worspace.

Data Source


In the Data Explorer View, specify a new Data Source. From the Wizard, select Ecore ODA Data Source on the first page.
Select a new Ecore Data Source.

On the second page, select the sample .extlibrary file in your workspace and "Ping" it to make sure that it can be loaded.
Select and test the sample file.

Data Set


Again in the Data Explorer View, specify a new Data Set. The Data Source that we just created should be automatically selected on the first page of the Wizard, and the Wizard should know that the Data Set Type is Ecore ODA Data Set.
Select the Ecore Data Source we just created.

On the next two pages, we will choose the Model Object that we are querying from, the columns that we are selecting to use for the query result, and we will fill in the OCL Condition that will restrict the query results.
A very simple query would be to find the names of all writers in all libraries. For this, we will
select Writer -> name,
from the invariant Writer,
where self.oclIsKindOf(Writer)

Add the OCL Conditional Query and select an Invariant.
If you do not select an invariant from the combo, then all EAttributes and EReferences in the Model will be available for selection. Try it out!

Select the Columns for the Report.
The EAttributes and EReferences displayed are based on the invariant selection on the previous Wizard page. Here, a Writer is also an Addressable Person, so the EStructuralFeatures for those model elements are also available.


After we are finished with the Wizard, we can Edit the Data Set properties, preview the results of the query, and modify our query conditional, selected columns and invariant.
Edit the Invariant and Query condition.
Edit the Invariant and the conditional query.

Edit the Columns for reporting.
Edit the columns that will appear in the report.

Preview the Query Results.
Preview the query results. You can switch among the pages, so preview different conditional queries, different invariants and different column selections if you feel adventurous!

Run a Report


Now that we have a Data Set, we can drag it into our report to add all the column data (in this case, there is just one column name). From the Preview subtab, we can then preview the report and see our query results!
Drag the Data Set into the report to set up the result set table.

Backport


A backport of these plug-ins for Eclipse 3.2, JDK 1.4 and EMF 2.2 is also available, but future support is not guaranteed. A description of these plug-ins and a comparison between the latest version and the backport is available here.

Part Two


Continue to part 2 of this guide for more advanced features ->

Thursday, January 17, 2008

Load XSDs Without Dependencies

Recently my colleague, Jeff Ramsdale, and I were faced with an Eclipse XSD problem: how can we load the contents of an XSD Resource without loading its transitive dependencies?
In our particular situation, we have the following requirements:
  • There are some logical groupings of xsd files.
  • In each grouping there are upwards of 150 schemas, some of which are very large.
  • Many of these schemas refer to one another or refer to schemas that refer to other schemas, etc.
  • We have a TreeViewer that displays these groups and we need to display the schemas in each group and the contents of each schema.
  • We have a custom ItemProviderAdapter for each schema group, and each group's children is a simple object that displays the schema name.
  • For each schema, the getChildren method of the ItemProviderAdapter uses a ResourceSet to demand-load the contents of the XSDResourceImpl to get its contents.
We found that the top level groups were displayed very quickly, but when we tried to open each schema (i.e., load it from the ResourceSet), it could take any number of seconds (10-20 in some cases) to display the schema contents.
Some schemas were causing many transitive dependencies to be loaded into the ResourceSet, even though the contents of those schemas were not displayed in the UI.
The solution, which we implemented thanks to a suggestion by Ed Merks, is to not use the ResourceSet for loading the XSDResourceImpl. We found that we could get the schema contents in much less time and without loading dependent resources just by instantiating the XSDResourceImpl directly with its corresponding URI.
Below I will show you how to set up a test case to demonstrate this solution.

Create a New Eclipse Project


Goto File -> New -> Other -> Plug-in Development -> Plug-in Project and create a project called transitive.dependencies.example.
We will need to declare the following Plug-in dependencies in the MANIFEST.MF
  • org.junit4
  • org.eclipse.emf
  • org.eclipse.emf.ecore
  • org.eclipse.xsd

Plug-in dependencies

Create XSDs With Transitive Dependencies


For this example I created 3 schemas (a.xsd, b.xsd and c.xsd) with transitive dependencies such that a.xsd refers to b.xsd, which refers to c.xsd, which refers to xml.xsd.
The three schemas are below and can be dropped into the folder /transitive.dependencies.example/xsd in our workspace.

a.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://transitive.dependencies.example/a"
xmlns:b="http://transitive.dependencies.example/b"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://transitive.dependencies.example/a">
<xsd:annotation>
<xsd:documentation>schema a ($Date: 2008/01/16 00:14:59 $) </xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://transitive.dependencies.example/b"
schemaLocation="xsd/b.xsd"/>
<xsd:attributeGroup name="a.attributes">
<xsd:attribute ref="b:b.type"/>
</xsd:attributeGroup>
</xsd:schema>


b.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://transitive.dependencies.example/b"
xmlns:c="http://transitive.dependencies.example/c"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://transitive.dependencies.example/b">
<xsd:annotation>
<xsd:documentation>schema b ($Date: 2008/01/16 00:14:59 $) </xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://transitive.dependencies.example/c"
schemaLocation="xsd/c.xsd"/>
<xsd:attribute name="b.type" type="xsd:anyURI"/>
<xsd:attributeGroup name="b.attributes">
<xsd:attribute ref="c:c.type"/>
</xsd:attributeGroup>
</xsd:schema>


c.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://transitive.dependencies.example/c"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://transitive.dependencies.example/c">
<xsd:annotation>
<xsd:documentation>schema c ($Date: 2008/01/16 00:14:59 $) </xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
<xsd:attribute name="c.type" type="xsd:anyURI"/>
<xsd:attributeGroup name="c.attributes">
<xsd:attribute ref="xml:id"/>
<xsd:attribute ref="xml:lang"/>
</xsd:attributeGroup>
</xsd:schema>


Create a New Test Case


For our first test, we want to show that when we demand-load an XSDResourceImpl through a ResourceSet, all of its transitive dependencies are also loaded into the ResourceSet.


package transitive.dependencies.example;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xsd.impl.XSDSchemaImpl;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.junit.Test;

public class LoadXsdTest {

@Test
public void demandLoadFromResourceSet() throws Exception {
final ResourceSet resourceSet = XSDSchemaImpl.createResourceSet();
assertNotNull(resourceSet.getResource(URI.createFileURI("xsd/a.xsd"),
true));
assertEquals(4, resourceSet.getResources().size());
assertNotNull(resourceSet.getResource(URI.createFileURI("xsd/b.xsd"),
false));
assertNotNull(resourceSet.getResource(URI.createFileURI("xsd/c.xsd"),
false));
assertNotNull(resourceSet.getResource(
URI.createURI("http://www.w3.org/2001/xml.xsd"), false));
}
}



For our next test, we want to show that when we load an orphaned XSDResourceImpl and manually put it into and retrieve it from the ResourceSet, that its dependencies are not automatically resolved.


....
@Test
public void createAndAddDetachedResources() throws Exception {
final ResourceSet resourceSet = XSDSchemaImpl.createResourceSet();
final URI uri = URI.createFileURI("xsd/a.xsd");
resourceSet.getResources().add(new XSDResourceImpl(uri));
assertEquals(1, resourceSet.getResources().size());

assertNotNull(resourceSet.getResource(uri, false));
assertEquals(1, resourceSet.getResources().size());
}
....


Interestingly, in the second test case, if we set the demandLoad flag to true when we get the XSDResourceImpl from the ResourceSet, the ResourceSet will load all the dependent resources, even though a.xsd has already been loaded and is present in the ResourceSet.  In that case, the second assertion will fail.

Both of the above test cases should pass as written.  Run them to get a green bar!

Wednesday, December 26, 2007

An EMF Editor for VoiceXML

The following entry provides a starting point for creating a basic EMF editor for VoiceXML. It is actually very straightforward and provides a good demonstration of the power of EMF for creating a basic, yet powerful editor (with cut-and-paste, drag-and-drop, syntax checking, etc.) for a language with an existing, well-defined schema. My goal here is not to provide a polished, finished editor, but a fully-functional starting point.

Download the VoiceXML Shemas from the W3C website


The VoiceXML schemas can all be downloaded from http://www.w3.org/TR/voicexml21
We will need the following 9 files:
  • vxml.xsd
  • vxml-attribs.xsd
  • vxml-datatypes.xsd
  • vxml-grammar-extension.xsd
  • vxml-grammar-restriction.xsd
  • vxml-synthesis-extension.xsd
  • vxml-synthesis-restriction.xsd
  • grammar-core.xsd
  • synthesis-core.xsd

From a unix shell, you can use the following command to retrieve these files:

wget http://www.w3.org/TR/voicexml21/vxml.xsd \
http://www.w3.org/TR/voicexml21/vxml-attribs.xsd \
http://www.w3.org/TR/voicexml21/vxml-datatypes.xsd \
http://www.w3.org/TR/voicexml21/vxml-grammar-extension.xsd \
http://www.w3.org/TR/voicexml21/vxml-grammar-restriction.xsd \
http://www.w3.org/TR/voicexml21/vxml-synthesis-extension.xsd \
http://www.w3.org/TR/voicexml21/vxml-synthesis-restriction.xsd \
http://www.w3.org/TR/voicexml21/grammar-core.xsd \
http://www.w3.org/TR/voicexml21/synthesis-core.xsd


Modify the VoiceXML Schemas


Unfortunately, the schemas cannot currently be converted directly into Ecore files, but the modifications you will need to make are minor.

In the vxml-attribs.xsd file, remove the import declaration for xml.xsd, or else we may see the following warning:
Warning | XSD: The location 'http://www.w3.org/2001/xml.xsd' has not been resolved | vxml-attribs.xsd | line 23
We can remove the declaration from the file in a shell with these commands:
  • perl -pi -e 's~<xsd:import namespace="http://www.w3.org/XML/1998/namespace"~~' vxml-attribs.xsd
  • perl -pi -e 's~schemaLocation="http://www.w3.org/2001/xml.xsd"/>~~' vxml-attribs.xsd

In the vxml-synthesis-extension.xsd, add an import declaration for vxml.xsd, or else we may see the following error:
Error | XSD: Model group reference 'http://www.w3.org/2001/vxml#executable.content is unresolved | vxml-synthesis-extension.xsd | line 93
We can add this declaration in a shell with this command:
  • perl -pi -e 's~<xsd:include schemaLocation="vxml-attribs.xsd"/>~<xsd:include schemaLocation="vxml-attribs.xsd"/>\n <xsd:include schemaLocation="vxml.xsd"/>~' vxml-synthesis-extension.xsd

Remove the redefinition of speak.attribs in vxml-synthesis-restriction.xsd and hard-code the restriction in synthesis-core.xsd, or else we may see the following error:
Error | XSD: The type of attribute '#version' must derive from '#version.datatype' | vxml-synthesis-restriction.xsd | line 34
We can hard-code this redefinition in a shell with these commands:
  • perl -pi -e 's~<xsd:attributeGroup name="speak.attribs">~~' vxml-synthesis-restriction.xsd
  • perl -pi -e 's~<xsd:attribute name="version" type="version.datatype" fixed="1.0"/>~~' vxml-synthesis-restriction.xsd
  • perl -pi -e 's~<xsd:attribute ref="xml:lang"/>~~' vxml-synthesis-restriction.xsd
  • perl -pi -e 's~<xsd:attribute ref="xml:base"/>~~' vxml-synthesis-restriction.xsd
  • perl -pi -e 's~</xsd:attributeGroup>~~' vxml-synthesis-restriction.xsd
  • perl -pi -e 's~<xsd:attribute name="version" type="version.datatype"/>~<xsd:attribute name="version" type="version.datatype" fixed="1.0"/>~' synthesis-core.xsd

In the vxml-synthesis-restriction.xsd file, remove the import declaration for xml.xsd, or else we may see the following warning:
Warning | XSD: The location 'http://www.w3.org/2001/xml.xsd' has not been resolved | vxml-synthesis-restriction.xsd | line 21
We can remove the declaration from the file in a shell with these commands:
  • perl -pi -e 's~<xsd:import namespace="http://www.w3.org/XML/1998/namespace"~~' vxml-synthesis-restriction.xsd
  • perl -pi -e 's~schemaLocation="http://www.w3.org/2001/xml.xsd"/>~~' vxml-synthesis-restriction.xsd

Replace the xsd:union references in synthesis-core.xsd with the regex restrictions for the unioned datatypes, or else we may see the following when trying to create an EMF project from the schemas:
Error: XSD: The 'memberTypes' attribute must be present or tehre must be contained member types : URI null Line 155 Column 2
Error: XSD: The 'memberTypes' attribute must be present or tehre must be contained member types : URI null Line 159 Column 2
Error: XSD: The 'memberTypes' attribute must be present or tehre must be contained member types : URI null Line 163 Column 2
Error: XSD: The 'memberTypes' attribute must be present or tehre must be contained member types : URI null Line 167 Column 2
Error: XSD: The value '100.0' of attribute 'default' must be one of the members types of 'http://www.w3.org/2001/vxml#volume.datatype' : URI null Line 365 Column 4
We can replace the xsd:union references in synthesis-core.xsd in a unix shell with the following commands:
  • perl -pi -e 's~<xsd:union memberTypes="hertz.number hertz.relative percent semitone height.scale"/>~<xsd:restriction base="xsd:string">\n <xsd:pattern value="(([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)Hz)|([+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)Hz)|([+\-]?([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)%)|([+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)st)|(x-high|high|medium|low|x-low-default)" />\n </xsd:restriction>~' synthesis-core.xsd
  • perl -pi -e 's~<xsd:union memberTypes="number percent speed.scale"/>~<xsd:restriction base="xsd:string">\n <xsd:pattern value="([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)|([+\-]?([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)%)|(x-fast|fast|medium|slow|x-slow|default)" />\n </xsd:restriction>~' synthesis-core.xsd
  • perl -pi -e 's~<xsd:union memberTypes="volume.number relative percent volume.scale"/>~<xsd:restriction base="xsd:string">\n <xsd:pattern value="(0*(100.[0-9]*|[0-9][0-9].[0-9]*|[0-9].[0-9]*|.[0-9]+))|([+\-]([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+))|([+\-]?([0-9]+|[0-9]+.[0-9]*|[0-9]*.[0-9]+)%)|(silent|x-soft|soft|medium|loud|x-loud|default)" />\n </xsd:restriction>~' synthesis-core.xsd


Create an EMF Project From the VoiceXML Schemas


We will need to install 2 plugin-projects in order to generate the EMF model and editor from the VoiceXML schemas:
Eclipse Modelling Framework (EMF) SDK
XML Schema Infoset Model (XSD) Extender SDK

We can install these through the Europa Discovery Site by following this path of menu options:
Help -> Software Updates -> Find and Install... -> Search For New Features To Install -> Europa Discovery Site -> Models and Model Development -> EMF Extender SDK and XSD Extender SDK
EMF Extender SDK and XSD Extender SDK

Now, we are ready to create the EMF Project with a standard menu: File -> New -> Project... -> EMF Project.
We can call the project org.w3.vxml.
Create the EMF Project in the New Project Wizard

We will select the XML model importer from the New Project Wizard.
Select the XML model importer from the New Project Wizard

We can select the nine schemas listed above for generating the genmodel file, which we will call vxml.genmodel.
Select the nine schemas listed above for generating the genmodel file

We can select to generate the packages org.w3._2001.vxml and org.w3.xml._1998.namespace (since the vxml package depends on the xml package).
Generate org.w3._2001.vxml and org.w3.xml._1998.namespace


Modify the Generated Ecore and Genmodel


This step is open-ended, as modifications to the ecore and genmodel files will be as needed, depending on how our target audience will use the editor. Here are a few quick cosmetic changes:
  • In the ecore file, remove any Type suffixes from the model objects (e.g., change AssignType -> Assign, BlockType -> Block, etc.). Unfortunately, since there are both a Meta and MetaType and Metadata and MetadataType elements in the ecore file, these names cannot be changed. Also, if we try to change ObjectType to Object, we will get errors in the generated code.
  • In the ecore file, change Audio -> BaseAudio and Audio1 -> Audio since Audio1 and not Audio is the element that is primarily used. Do the same for Mark and Mark1, SayAs and SayAs1, VersionDatatype and VersionDatatype1, and MixedGrammar, MixedGrammar1 and MixedGrammar11.
  • In the genmodel file, change the Namespace Base Package from org.w3.xml._1998 to org.w3.xml. Likewise, change the VXML Base Package from org.w3._2001 to org.w3.vxml.


Generate the Model, Edit and Editor Code


From the root Vxml Node in the genmodel file, generate the Model code, then the Edit code and then the Editor code. Just like that, the Model Objects, the ItemProviders for displaying the Model in the UI and the multi-tab Editor code has all been generated. So let's try it out!
Generate the model, edit and editor code.

Start Coding VoiceXML


On the Overview page of the /org.w3.vxml.editor/META-INF/MANIFEST.MF form editor, in the Testing section, we will click on Launch an Eclipse application.
In the new Workbench, we will create a New General project and call it hello.world.vxml.
Now, from the New -> Other... wizard, we will create a new Vxml Model (Under Example EMF Creation Wizards) and call it HelloWorld.vxml.
Create a new Vxml Model.

We will select Vxml as the Model Object and Finish the Wizard.
Use Vxml as the Model Object.

In the Editor that is now opened, we can start writing Vxml!
Hello World Vxml in the generated editor.

We can view the emitted VoiceXML with the Text editor.
The emitted Hello World Vxml code.