Xcore Comprehensive Overview

Summary

Xcore is an extended concrete syntax for Ecore that, in combination with Xbase, transforms it into a fully fledged programming language with high quality tools reminiscent of the Java Development Tools. You can use it not only to specify the structure of your model, but also the behavior of your operations and derived features as well as the conversion logic of your data types. It eliminates the dividing line between modeling and programming, combining the advantages of each. All this is supported for both generated and dynamic EMF models.


Contents

  1. Creating an Xcore Project
  2. Creating an Xcore model
    1. Specifying a Package
    2. Specifying a Class
    3. Specifying an Attribute
    4. Specifying a Containment Reference
    5. Specifying a Container Reference
    6. Specifying a Cross Reference
    7. Specifying an Enumeration
    8. Specifying a Data Type
    9. Specifying an Operation
    10. Specifying a Derived Feature
    11. Implementing an Interface
    12. Specifying an Annotation
  3. Creating an ESON Textual Instance
  4. Creating a Dynamic Instance
  5. Configuring GenModel Properties
  6. Converting a GenModel to an Xcore Model
  7. Converting an Xcore Model to a GenModel

Creating an Xcore Project

Xcore can be used in any properly-configured Java project. There's a convenient wizard for creating an empty pre-configured project.

In your workspace you'll see your new project.

XcoreNewlyCreatedLibraryProject.png

It's a project with a Java, PDE, and Xtext natures that contains the following:

Of course it's possible to start with any Java project and use the Convert → Add Xtext Nature or Convert → Convert to Plug-in Projects to introduce the missing natures and to edit the MANIFEST.MF to add missing dependencies.

Creating an Xcore model

An Xcore model is created in the model folder via its context menu using New → File to create an empty new file with extension .xcore, e.g., Company.xcore. This will open the Xcore-aware editor. It starts out with an error marker, because an empty file isn't a valid Xcore instance.

Specifying a Package

The editor supports syntax completion so if you enter Ctrl-Space, you'll see it fills in the package keyword. Next you need to enter a package name, e.g., org.example.company. If you save, you'll see the error markers go away. You've created an empty Xcore package. Files build.properties, plugin.properties and plugin.xml are automatically created at the top level of the project during the save. As you can see, like Java, an Xcore model starts with a package declaration but note that that there's no semicolon. Note too that nothing is generated in the src-gen folder yet; EMF doesn't generate anything for empty packages.

XcoreNewPropertiesAndXMLFilesCreatedOnSave.png

Specifying a Class

Now you're ready to create something more meaningful. Add two blank lines and try hitting Ctrl-Space again to see what you're allowed to enter next.

XcoreTopLevelSyntaxCompletion.png

We'll start by defining a class called Library. After entering the class keyword and the name of the class, type a curly brace: the closing curly brace is automatically inserted. If you now save the editor, you'll see the following:

XcoreEmptyLibraryClass.png

Notice that the model code has been generated automatically.

Specifying an Attribute

Now you're ready to define the structure of the Library class. Within the curly braces, hit Ctrl-Space to see what you're allowed to enter next.

XcoreClassLevelSyntaxCompletion.png

To specify an attribute, you need to specify the name of a data type followed by the name of the feature. So if you choose String, enter name, and save, you'll see the following:

XcoreAttributeDefinition.png

Of course the corresponding feature accessor methods, i.e., getName() and setName(String), will immediately be generated in the Library interface. Note that while the Xcore source shows the use of String, which in the completion proposal you saw earlier was listed as java.lang.String, from the hover information you can see that the reference actually resolves to the EString data type from the built-in Ecore package. Xcore provides familiar aliases for all of the Ecore data types that correspond to Java built-in and mathematical types. If your attribute is a reserved word such as type or id, you will need to escape it with a ^. For example: String ^type.

class Library
{
  int ^id
  String ^type
}

If you need to specify a default value for an attribute, you can use the following syntax:

class Library
{
  String name = "Default Name"
  boolean stateOwned = "true"
}

In the above example, the string after the equals sign will be used to populate the Default Value Literal property of the Ecore feature. Thus, the generated Java implementation code will contain:

public class LibraryImpl ...
{
  protected static final String NAME_EDEFAULT = "Default Name";
  protected static final boolean STATE_OWNED_EDEFAULT = true;
  ...
}

Specifying a Containment Reference

We'll want libraries to contain books and authors, so let's add two more empty classes, Book and Writer so that we can specify references to them in the library. If we try the same approach as defining the name feature, you'll end up with the following:

XcoreAttributeWithClassTypeError.png

That's because Xcore interprets this as an attribute and the type of an attribute must be a data type, not a class. If you hover over the error indicator, you'll see how to fix the problem.

XcoreAttributeWithClassTypeErrorQuickFix.png

In this case, we want to define a containment reference, so choose that option. Notice that if you select the reference to Book you can use F3 to navigate to the definition for the Book class. Of course we wanted to define this to be a multi-valued reference and here have specified a single-valued reference. The multiplicity of a feature is specified with the bounds in square brackets, where [] is used as a short-hand for [0..*] as follows:

XcoreMuliValuedReference.png

Let's define another multi-valued containment reference called authors and save the result, which will look as follows:

XcoreLibraryWithThreeFeatures.png

Again, the expected results are immediately generated. Notice that the outline view uses icons are much like the GenModel's icons, though with a purplish cast rather than a bluish one.

Specifying a Container Reference

If you wanted a convenient API for navigating from a Book or Writer to its containing Library you'd specify container reference with an opposite like this:

XcoreContainerReferenceWithCompletion.png

Notice that completion support is available for specifying the opposite. When you select a choice, the opposite is automatically updated to refer back.

XcorePairedOppositeReferences.png

Let's add a container feature for Writer as well, and let's define title and pages features for Book and a name feature for Writer to produce the following:

XcorePartiallyCompleteLibraryModel.png

Note how we've made use of int as an alias for EInt to define the type for pages.

Specifying a Cross Reference

The most important thing left to complete the picture is specifying the bidirectional relationship between Books and Writers. Specifying a cross reference is done much like we did for containment and container references, but using the refers keyword. We start by specifying one of the two references, i.e, a Book has authors, then the other, i.e., a Writer has books, at which point we can also specify the opposite.

XcoreCrossReferenceOppositeCompletion.png

Notice that opposite completion is supported here as well and that upon completion, both opposites are properly paired.

XcoreCrossReferenceCompletedOpposites.png

Of course F3 navigation is supported for the reference to an opposite.

References can be declared with the local modifier keyword. That is equivalent to setting the Ecore Resolve Proxies attribute to false.

Specifying an Enumeration

Supposed we wanted to categorize the kinds of books we have in the library. We could specify an enumeration named BookCategory for that and use it to specify a feature named bookCategory in Book as follows:

XcoreEnumeration.png

If you want to specify the integer values for the enum:

 enum BookCategory
 {
   Mystery = 0
   ScienceFiction = 1
   Biography = 2
 }

If you want to specify the literal values for the enum:

 enum BookCategory
 {
   Mystery as "M"
   ScienceFiction as "S"
   Biography as "B"
 }

If you want to specify both the integer value and the literal values for the enum:

 enum BookCategory
 {
   Mystery as "M" = 0
   ScienceFiction as "S" = 1
   Biography as "B" = 2
 }

Specifying a Data Type

Suppose we wanted to specify the copyright date of a book. Of course we could use EDate from Ecore, but its serialization format is more like date and time, so that might not exactly fit our needs. Instead we could define our own Date data type to wrap java.util.Date as follows:

XcoreDataTypeCompletion.png

A data type acts as a wrapper for an existing Java type, so the completion proposal supports choosing a Java class that matches the name we've started typing. Upon selection, an import is added, so we can use the short form of the name.

XcoreDataTypeWithCompletedImport.png

Because classifier names and Java type names can never be used interchangeably in any Xcore scope, it's not a conflict to import the Java name in a scope that also defines a classifier with the same name.

So far everything we've shown are things you could do with Ecore directly, so we've only seen how Xcore provides a concise textual syntax for Ecore. To complete the support for our own Date type, it would normally be necessary to modify the generated LibraryFactoryImpl class' methods that implement string conversion for this data type. With Xcore, we can specify this logic directly in the model. Note that if we look at the proposals for what may follow the type specification, we can see that the create and convert keywords are expected.

XcoreDataTypeCreateMethodCompletionProposal.png

So we can choose to start specifying the create logic for creating an instance of the data type from a String value which is specifying within the curly braces of a block. We can using Java's java.text.SimpleDateFormat class as follows:

XcoreDataTypeCreateMethodConstructorCompletion.png

Notice that completion proposals understand what's on the classpath so we can use that for calling the constructor. To refer to the instance of the data type within the body we use "it", which acts much like the implicit this variable in Java. We also need to consider that parse can throw a ParseException that we need to handle properly. In addition, the value of the data type might be null, so best we guard for that case too. And finally, we can use a similar approach to specify the convert logic to produce the following complete result:

XcoreBookWithCopyrightAttribute.png

Notice we've also used the new data type to define a copyright feature in the Book class and that all the Java classes we've used were automatically imported by completion. The notation for expressing behavior in Xcore uses the model defined by Xbase. It's very similar to Java, but is expression oriented, so even things that are statements in Java, return a value in Xcore/Xbase. That's why you don't see a return statement.

Specifying an Operation

Suppose you wanted convenience methods on Library, e.g., an operation to retrieve a Book give its title. We could specify that as follows:

XcoreOperationBodyForLoopCompletion.png

Again, the syntax is very Java-like and of course completion proposals are aware of the Java APIs implied by the Xcore model definition so feature names such as books are in the proposals. Here's how we would complete the definition:

XcoreOperationComplete.png

We return null if we reach the end of the loop without a match. That's all there is to it. If you look at the generated implementation class for Library you'll see it compiles to the following Java code:

XcoreCompiledOperation.png

Notice that the == comparison for title is compiled to a null-safe Object.equals test. Notice also that all the things that look like simple field accesses in the Xcore code actually compile down to proper calls to the generated API accessor methods. One of the nice things about Xcore is that you get the advantages of writing what look like simple Java classes with simple fields declarations, including the ability to write a notation as if you had simple fields, but you end up with properly defined APIs and proper uses of them.

To return Lists from operations, don't use the Java syntax but XCore syntax, e.g., Book[] getBooks(String filter) instead of List<Book> getBooks().

To have your operation return any Java type, define it as a Data Type first; see previous section.

Specifying a Derived Feature

With Xcore it's even possible to define the behavior of a derived feature. You only need to mark the feature as derived and, using the get keyword, define within a block how the value is completed. For example, you could specify a derived attribute to compute the last name of the Writer as follows:

XcoreDerivedAttribute.png

Implementing an Interface

If your class needs to implement an interface such as java.lang.Iterable, you wrap it just as you would a type. For examplexample:

 type Iterator wraps Iterator<EObject>
 interface Iterable wraps java.lang.Iterable<EObject>
 {}
 class MyClass extends Iterable
 {
   op Iterator iterator()
   {
     return new SpecialIterator();
   }
 }

Specifying an Annotation

Ecore supports general-purpose annotations comprising a source string (typically a URI) for specifying identification and a map of string-based key-value pairs for specifying the details. Rather than requiring the typically large source URI to be repeated on each use, Xcore provide support for specifying a simple alias for it as follows:

XcoreAnnotationDefinition.png

We can then use it as follows:

XcoreAnnotationUse.png

Annotations with the GenModel's nsURI have specialized support in Xcore. Any key that matches the name of a feature in the GenModel, will be used to populate that feature. If you have a look at your generated model after saving the above, you will notice that the base class of each modeled class's implementation class has changed to use the one we've just specified.

XcoreGenerateClassWithSpecifiedRootExtendsClass.png

Note that even the annotation definition itself is not necessary for this case because this "alias" is defined in xcore.lang and is therefore visible everywhere by default:

XcoreLang.png

Creating a Dynamic Instance

Not only does Xcore generate you model code as you'd expect, everything we've specified works in dynamic models as well. Let's create a dynamic instance of Library by selecting it and bringing up the context menu for it as follows:

XcoreCreateDynamicInstanceMenu.png

This brings up the following wizard, which lets you control the name and location of the new instance:

XcoreCreateDynamicInstanceWizard.png

The defaults should be fine for now do drive the wizard to completion. This will open the Sample Reflective Editor as follows:

XcoreDynamicLibraryInstance.png

Expanding the first resource and double clicking on the Library instance will bring up the properties view. Notice that the Library.xcore resource is loaded in order to load the dynamic Library instance. If you expand it, you'll notice it contains the Xcore model, the GenModel, the Ecore model, and a bunch of JVM model instances. The GenModel and Ecore model are derived from the details in Xcore model. The various JVM model instances in turn are derived from the GenModel, i.e, all the information about the Java artifacts that are generated by the GenModel. Additional resources contain things that are referenced directly and indirectly from our model. Let's make the model more complete by creating a Book.

XcoreCreateBookInstance.png

We can do the same thing to create a Writer. Let's have a look at the properties for a Writer. We can given the Writer a Name, say Arthur C Clark using the properties view.

XcoreWriterSetName.png

Notice that the Last Name updates correctly whenever we change the full Name.

Let's populate some of the information about the Book. We'll call it 2001: A Space Odyssey and set the copyright date to September 1st, 1968.

XcoreBookSetCopyright.png

Notice that as we start entering a date, we get feedback about parse failures demonstrating that the data type conversion logic we implemented is hard at work.

Finally, we can have a look at how the logic for finding a book from its title is working in the Library instance. Let's select it and use Windows → Show View → Other... and locate Eclipse Modeling Framework → Operation Invocation View. This view responds to the selection in the active editor and provides a drop-down for choosing which operation to invoke. If the operation has arguments, it provides properties for setting those values. And of course there is an Invoke button for actually invoking the operation. We'd can use it as follows:

XcoreLibraryOperationInvocation.png

After invoking the operation, the result will be selected in the editor, so when the right book is selected, we can see that this too is working well.

Configuring GenModel Properties

The Xcore editor directly supports the same Properties view as you're familiar with from the Generator. Use Windows → Show View → Other... followed by General → Properties to bring up the Properties view. It responds to selection it the editor and the Outline, so selecting the package produces the following:

XcorePropertiesView.png

Note that the properties for the GenModel and the GenPackage are merged into a single set of properties.

Changing the values in the Properties view will modify the source with the corresponding @GenModel annotations.

XcorePropertiesViewSettingEditDirectory.png

If you save the Xcore resource now, you'll see that the org.example.library.edit project is created and the item providers are generated there.

XcoreGeneratedEditProject.png

Converting a GenModel to an Xcore Model

To make it easy to migrate existing artifacts to Xcore, we've added migration support. It is integrated with the GenModel's exporter framework, so from the context menu of the Generator editor opened for a *.genmodel resource, you can invoke Export Model.... Suppose we did that for the XML Schema-based library model.

XcoreExportGenModelToXcore.png

That will bring up the "Export EMF Model" wizard from which you can choose Xcore as the target.

XcoreExportWizardExporterPage.png

The next page allows you to choose where the new Xcore resources will be saved.

XcoreExportWizardDestinationPage.png

The final page allows you to choose which packages to export and the name of the Xcore resource in which to save them.

XcoreExportWizardFinalPage.png

Completing that page produces the following resource:

XcoreConvertedSchemaLibraryModel.png

Notice all the annotations that capture details that would otherwise be missing:

Notice too in the import directives that keywords can be used as identifiers when escaped with a ^.

Converting an Xcore Model to a GenModel

If for some reason you need the legacy format, e.g., to use a tool which expects that format, you can convert your Xcore model to a GenModel using the importer framework. From the context menu for the *.xcore resource, you can create a new *.genmodel resource as follows:

XcoreGenModelImportContextMenu.png

This brings up the following wizard in which we can choose to create a new EMF Generator model:

XcoreGenModelImportWizardSelectionPage.png

Proceeding to the next page, where we can choose the location and name of the new *.genmodel resource.

XcoreGenModelImportWizardGenModelNamePage.png

Proceeding from there, we can choose which importer to use.

XcoreGenModelImportWizardImporterSelectionPage.png

After that, we can choose which *.xcore resource to import. Note that one is already suggested because we have it selected in the package explore. Also note that you must hit the Load button in order to proceed from this page

XcoreGenModelImportWizardSourceXcorePage.png

That brings us to the final page where we can choose which packages to import and which to reuse from existing sources.

XcoreGenModelImportWizardPackageSelectionPage.png

Given that the Xcore resource does physically contain a GenModel and an Ecore model, it should be possible in the future for any tool to consume Xcore resources directly.