Jump to: navigation, search

Xcore

{{#eclipseproject:modeling.emf.emf}}

Modeling for Programmers and Programming for Modelers

XcoreLogo.png

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.

Getting Started

The first version of Xcore will be part of the Juno release train and is currently available in the Juno p2 repository. To get started, you'll need to install the hardware-appropriate platform version of Eclipse 4.2 or Eclipse 3.8. Once you have that installed and running, you're ready to install Xcore.

  • Use "Help → Install New Software..." to bring up the "Install" wizard; the Juno repository should be available in the "Work with" dropdown.
  • Select Juno and wait for the content of the wizard to populate.
  • Either enter "Xcore" in the filter field or locate "Modeling → EMF - Eclipse Modeling Framework Xcore SDK" in the content and check mark it.
  • Drive the wizard to completion.

Xcore and everything it needs will be automatically installed. Of course you'll need to restart Eclipse for the changes to properly take effect.

Please keep in mind that this is all work in progress, so there are bound to be bugs. I've fixed a few of them while writing this tutorial; those fixes won't be available until the next build (M6) is promoted to Juno. For bleeding edge changes, you can use the Xtext nightly builds and EMF nightly builds; the install wizard supports drag and drop so you can drag the Xtext link first and then the EMF link before following the steps above to locate Xcore and install its latest nightly version. If you have questions, the EMF newsgroup (eclipse.tools.emf) is the best place to ask them; please use "[xcore]" as the prefix for your subject line.

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.

  • Use "File → Project..." and enter "Xcore" in the filter field or locate "Xcore → Xcore Project".
  • Use "Next" to advance the "New Project" wizard and enter the name of your project. It's best to use a qualified name that will be appropriate as a plug-in ID and as a prefix for your Java packages, i.e, org.example.library.
  • Use "Finish" to complete the wizard.

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:

  • A "src" folder for your hand written Java code.
  • A "src-gen" folder in which your model code will be generated by default.
  • A MANIFEST.MF with prepopulated dependencies for EMF Ecore and Xtext Xbase.
  • And an empty model folder.

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. 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.

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

Noticed 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 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.

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"
 }


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" features 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 (E)List<Book> getBooks().

To have your operation return any Java type, define it as a Data Type first (see previous chapter).

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. Example:

 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:

  • The "nsURI" of the model, which, if unspecified by an "Ecore" annotation, is just the fully qualified package name.
  • The "ExtendedMetaData" annotations which record the mapping onto XML Schema.

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.