- Implementing Domain:Specific Languages with Xtext and Xtend
- Lorenzo Bettini
- 743字
- 2021-08-13 16:26:18
The Eclipse Modeling Framework (EMF)
The Eclipse Modeling Framework (EMF) (Steinberg et al., 2008), http://www.eclipse.org/modeling/emf, provides code generation facilities for building tools and applications based on structured data models. Most of the Eclipse projects that in some way deal with modeling are based on EMF since it simplifies the development of complex software applications with its modeling mechanisms. The model specification ( metamodel) can be described in XMI, XML Schema, UML, Rational Rose, or annotated Java. It is also possible to specify the metamodel programmatically using Xcore, which was implemented in Xtext. Typically, a metamodel is defined in the Ecore format, which is basically an implementation of a subset of UML class diagrams.
Tip
Pay attention to the meta levels in this context: an Ecore model is a metamodel, since it is a model describing a model. Using the metamodel EMF produces a set of Java classes for the model. If you are not familiar with modeling technologies, you can think of a metamodel as a way of defining Java classes (that is, hierarchy relations, fields, method signatures, and so on). All Java classes generated by EMF are subclasses of EObject
, which can be seen as the EMF equivalent of java.lang.Object
. Similarly, EClass
corresponds to java.lang.Class
for dealing with introspection and reflection mechanisms.
Xtext relies on EMF for creating the AST, Abstract Syntax Tree, which we talked about in Chapter 1, Implementing a DSL. From your grammar specification, Xtext will automatically infer the EMF metamodel for your language. You can refer to the Xtext documentation for all the details about metamodel inference. For the moment, you can consider this simplified scenario: for each rule in your grammar, an EMF interface and class will be created with a field for each feature in the rule (together with a getter and setter). For instance, for the Entity
rule, we will have the corresponding Java interface (and the corresponding implementation Java class):
public interface Entity extends EObject { String getName(); void setName(String value); Entity getSuperType(); void setSuperType(Entity value); EList<Attribute> getAttributes(); }
Since these Java artifacts are generated, they are placed in the corresponding package in the src-gen
folder.
You can have a look at the generated metamodel file Entities.ecore
by opening it with the default EMF Ecore editor. Although you may not know the details of the description of a metamodel in EMF, it should be quite straightforward to grasp the meaning of it (refer to the following screenshot; in the screenshot, you can also see some expanded Java interfaces generated by EMF):
The inference of the metamodel and the corresponding EMF code generation is handled transparently and automatically by Xtext. However, Xtext can also use an existing metamodel that you maintain yourself, as detailed in the documentation (we will not use this mechanism in this book).
Since the model of your DSL programs are generated as instances of these generated EMF Java classes, a basic knowledge of EMF is required. As soon as you have to perform additional constraint checks for your DSL and to generate code, you will need to inspect this model and traverse it.
It is easy to use the generated Java classes since they follow conventions. In particular, instances of EMF classes must be created through a static factory (which results from EMF generation itself), thus there is no constructor to use; initialization of fields (that is, features) can be done with getters and setters. A collection in EMF is implemented as an EList
interface (which is an extension of the standard library List
). With only these notions in mind, it is easy to programmatically manipulate the model of your program. For instance, this Java snippet programmatically creates an Entities model that corresponds to an Entities DSL program:
import org.example.entities.entities.Attribute; import org.example.entities.entities.EntitiesFactory; import org.example.entities.entities.Entity; import org.example.entities.entities.Model; public class EntitiesEMFExample { public static void main(String[] args) { EntitiesFactory factory = EntitiesFactory.eINSTANCE; Entity superEntity = factory.createEntity(); superEntity.setName("MySuperEntity"); Entity entity = factory.createEntity(); entity.setName("MyEntity"); entity.setSuperType(superEntity); Attribute attribute = factory.createAttribute(); attribute.setName("myattribute"); attribute.setArray(false); attribute.setType(superEntity); entity.getAttributes().add(attribute); Model model = factory.createModel(); model.getEntities().add(superEntity); model.getEntities().add(entity); } }
EMF is easy to learn, but as with any powerful tool, there is much to learn to fully master it. As hinted previously, it is widely used in the Eclipse world, thus you can consider it as an investment. Notably, the new Eclipse platform, called e4, uses an application model based on EMF, thus if you plan to develop RCP applications based on the new Eclipse e4, you will have to deal with EMF.