Skip to content

Building the Droid DSL editor with Xtext

Arcnor edited this page Sep 16, 2011 · 13 revisions

Summary

This tutorial covers the instalation of Xtext and the creation of a DSL to model Android Applications.

Instalation and Requisites

  1. Eclipse IDE for Java Developeers, version 3.6.2 (Helios SR2 release).
    Available at: http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/heliossr2
  2. Xtext (version 1.0.2)
    Install "Xtext SDK" plugin from the built-in "Helios" update site (http://download.eclipse.org/releases/helios) under the "Modeling" category.

Modeling, Models, MDD

Our main objective is the creation of a textual editor that assists the creation of Android Applications by using an specific textual language. So, since we are going to talk about Android Applications, that is going to be our language domain (the subject of our language).

Once defined our language domain, we must to decide on choosing a (textual) syntax to our language that must represent every component of our domain.

Finally, we must create a parser that is capable of processing a file written acording to this textual syntax (a source file) and build an appropriate instance of our domain model.

That particular kind of language is called Domain Specific Language: a language specific to a particular domain.

Xtext

Xtext provides the tooling needed to create and use textual DSLs in a MDD environment. The language's domain is captured and described in a metamodel (the language's abstract syntax) and the language's textual syntax (it's concrete syntax) is described in a grammar file (.xtext file). Xtext can even infer your language metamodel (an Ecore model) from the language grammar.

Xtext will also create a parser and an editor to your language (with all the Eclipse editor features included).

The Android Application Domain

The Android Operating system has its own concepts regarding application development. An Application consists of: Activities, Resources and Layouts.

Activity

From Android Dev Guide

An Activity is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map.

Resources

From Android Dev Guide

In addition to code, an application consists of resources that are separate from source code, such as images, audio files, and anything relating to the visual presentation of the application.

Layouts

Layouts are collection of Views that define a hierarchy of widgets (UI objects).

DSL abstraction

To create a DSL, the first step is to decide the level of abstraction that it will deal with. The finer the granularity level, the more detailed the DSL is going to be (and more work to be built). So we chose a coarser granularity in order to make things easier.

We created an abstraction of the original Android concepts with the following concepts:

  • Application: The root concept of our abstraction
  • Screen: An application have several screens that display UI to users.
  • Widgets, Resources and Layouts: The same concepts but with less properties than the original concepts.

Creating the Xtext project

commit 16cdb466000acab21c7bf31c91b7e0f49c0d2ca8

Creating the Xtext projectCreating the Xtext project

To create a Xtext project, open the "Xtext Project Wizard":

  1. Open menu "File > New > Project..."
  2. Choose "Xtext > Xtext Project"

And fill the project properties:

  • Change the "Project name" to "org.xtext.example.droid"
  • Under the "Language" section
    1. Change the "Name" to "org.xtext.example.droid.Droid"
    2. Change the "Extension" to "droid"
  • Click "Finish"

Project layout

  • Grammar File (Droid.xtext): A grammar has two purposes: First, it is used to describe the concrete syntax of your language. Second, it contains information about how the parser shall create a model during parsing.
  • Language generator Worflow (GenerateDroid.mwe2): This workflow is used to generate the various language components (metamodel, parser, editor UI, etc) from an existing grammar file.

The Grammar Language

Xtext has its own grammar definition language so the rest of the tutorial is about the basics of this language. You can find more info on this language in the Xtext User Manual.

The grammar file (Droid.xtext) specifies the textual concrete syntax of your DSL by defining rules to the parser.

Generating Language Artifacts

commit 4a33a8373b42ce92c9424e8891fc600620060090

Generating Language ArtifactsGenerating Language Artifacts

In order to use the DSL you need to generate the language components.

  1. Right click the GenerateDroid.mwe2 workflow file.
  2. From its context menu, choose "Run As > MWE2 Workflow"

Installing ANTLR 3 parser generatorInstalling ANTLR 3 parser generator

At the first time, the generator will warn yout about the instalation of the ANTLR 3 parser generator. Go to the console, type 'y' and press "Enter". This allows the generator to download the parser generator based on ANTLR 3.

The workflow will generate all the files needed to create an Eclipse plugin with a textual editor to your DSL. Every time you make any change to the grammar file (Droid.xtext) you should regenerate the language artifacts.

Running the generated IDE plug-in

Screencast

Screencast

If the code generation succeeded, we are now able to test the IDE integration.

  1. Right-click on the Xtext project (org.xtext.example.droid)
  2. Choose "Run As > Eclipse Application".

This will starts a new Eclipse workbench with your DSL plug-ins installed.

Testing the generated editor

In the new workbench create a new project and therein a new file with the .droid file extension. This will open the generated Droid editor.

Try it a bit and discover the default functionality for code completion (Ctrl+Space even for cross-references), syntax highlighting, syntactic validation, linking errors, outline, find references etc.

You may notice that the editor ignores 'white-space characters' between tokens (new line, space, tab, etc) and has built-in support to both single and multi-line comments (// and /* */).

This will be our test project.

The Application concept

commit e75c45d1ae4457d8cdae5c705e5a4d7978de3c11

The first concept in our DSL is the "Application" concept. Add these lines to the grammar file:

Application:
	'application' name=STRING
;

This is a parser rule. A parser rule describes the concrete syntax of a specific class of your metamodel, that is, the pattern used by the parser in order to identificate an instance of a specific class in a source file.

In our example, the parser creates an Application object when it evaluates a source file and finds the word 'application' followed by a STRING (like "first app"). Simply put, an Application object is represented textually by the keyword 'application' followed by a STRING.

The name=STRING is an asignment. Assignments are used to assign the parsed information to a property (a feature) of the current object. It means that the string found will be the value of the name property of the created object.

The keyword STRING is a terminal. It is made available to your grammar by the inclusion of another grammar inside your grammar (through the with org.eclipse.xtext.common.Terminals statement). The grammar will evaluate the tokens defined in the STRING terminal rule and convert them to a string value.

Moreover, this first rule in the grammar has a special meaning: that is the entry rule of your grammar. Therefore, to create a valid source file (an instance of your model) you must start creating an object of the entry rule's type, i.e., an Application.

In order to test this change you need to regenerate the language artifacts and run the generated plugin. Do it now.

Application properties

commit 052f05e9eb5cdc0ab3a9800e5c678658017d7def

An Android application also has a package name, where the generated Java files are going to be placed. Let's add this property:

Application:
	'application' name=STRING
	  '=>' packageName=ID
;

This time I created a packageName property that will receive the value of an ID terminal found. An ID is a identifier, like xtext_is_cool. Let's test the editor.

Open the sample project and create a .droid file:

application "My First App" => myfirstapp

Save the file and it should work. Now try the following example:

application "My First App" => org.xtext.example.droid.myfirstapp

And you'll see an error: mismatched input '.' expecting EOF. That's because the parser have found an ID (org) and was expecting to reach the end of the file. If we want to use a qualified name (like org.eclipse.test) as the value of the package property we need to create a rule to recognize several tokens as a single value.

Datatype rules

commit 4e8dce9f17bc419f901e4438b4b6ef98a7e209d8

What is a qualified name? It can be just an ID but can also be an ID followed by a . and another ID. The pattern '.' followed by an ID can be repeated several times. This is enough to creating our datatype rule describing the PackageName datatype:

PackageName:
	ID ('.' ID)*
;

A datatype rule describes the concrete syntax of a datatype, which can be used as the value of a property.

Now we change the Application rule to use this datatype in the packageName property:

Application:
	'application' name=STRING
		'=>' packageName=PackageName
;

And the package is working as expected.

The Screen concept

commit afd8e816d7b14ce0d0ffe6ac91894283cc3c4b78

We can create the Screen concept in a similar way:

Screen:
	'screen' name=ID
	'{'

	'}'
;

But now we need to associate an Application to one or more Screens.

Concept assignments

commit 72a3d4af18dbf28f8bceae7e53d023a7dedad2fe

We can associate an Application to one or more Screens by creating an assignment rule, like we did to assign a STRING terminal to an Application when creating the name property:

Application:
	'application' name=STRING
		'=>' packageName=PackageName
	(screens+=Screen)+
;

This assignment is pretty similar to the previous ones, except by the += and + operators.

Assignment operators

From Xtext manual:

There are three different assignment operators, each with different semantics.

  • The simple equal sign ' =' is the straight forward assignment, and used for features which take only one element.
  • The ' +=' sign (the add operator) expects a multi-valued feature and adds the value on the right hand to that feature, which is a list feature.
  • The ' ?=' sign (boolean assignment operator) expects a feature of type EBoolean and sets it to true if the right hand side was consumed independently from the concrete value of the right hand side.

Expression Cardinality Operators

Again, from Xtext manual:

There are four different possible cardinalities:

  1. exactly one (the default, no operator)
  2. one or none (operator ?)
  3. any (zero or more, operator *)
  4. one or more (operator +)

That way, we are saying that after the packageName property, an Application must have ONE OR MORE assignments of Screen objects to the multi-valued screens property.

Run the editor and the following source file should be valid:

application "My First App" => org.xtext.example.droid.myfirstapp

screen Welcome { }

screen Main { }

Speeding Up

Now we are going to introduce some more concepts to our DSL: Layout, View, Widget.

Layout and Views

commit c5ee45f8b8870dacabef8d849d5b33c7484ad821

Add these concepts to the grammar:

Layout:
	'layout' name=ID
	'{'
    views=ViewCollection
	'}'
;

ViewCollection:
	('#' views+=View)+
;

View:
	'view'
;

And change the Application to include layouts in addition to the existing screens:

Application:
	'application' name=STRING
		'=>' packageName=PackageName
	(screens+=Screen | layouts+=Layout)+
;

It states that where we previously should have ONE OR MORE Screen assignments, now we must have ONE OR MORE Screen assignments OR Layout assigments (the | operator). Note that both assignments are being made to multi-valued features. Note also that the assignments can occur in any order (try it).

After the modification, this sould be a valid source file:

application "My First App" => org.xtext.example.droid.myfirstapp

screen Welcome { }

layout Default {
	# view
	# view
	# view
}

screen App { }

Another interesting feature is using the | operator to create unassigned rule calls. This allows us to work with parser rules like abstract classes.

Unassigned Rule Calls

commit be6e4c3f6cf85c75f69b5558fe4c78208da420d4

From Xtext manual:

Unassigned rule calls (the name suggests it) are rule calls to other parser rules, which are not used within an assignment. If there is no feature the returned value shall be assigned to, the value is assigned to the “to-be-returned” result of the calling rule.

For example, an layout can have Widgets or nested Layouts composing its CollectionView, so a View rule must delegate to a Widget or Layout rule.

View:
	Widget | Layout
;

Now we are going to use the same pattern to delegate the abstract Widget concept to the concrete widgets.

Widget:
	TextView | Button | ImageView | EditText | Spinner
;

TextView:
	'textView' (name=ID)?
		text=STRING
	'{'
	'}'
;

Button:
	'button' (name=ID)?
		( 'text' '=' text=STRING | 'image' '=' src=STRING )
	'{'
	'}'
;

ImageView:
	'imageView' (name=ID)?
		src=STRING
	'{'
	'}'
;

Spinner:
	'spinner' (name=ID)?
		prompt=STRING
	'{'
	'}'
;

EditText:
	'editText' (name=ID)?
		text=STRING?
	'{'
	'}'
;

Example:

application "My First App" => org.xtext.example.droid.myfirstapp

screen Welcome { }

layout Default {
	# textView "Welcome to First App" { }

	# layout Sublayout {
  	# textView "Put your name" { }
	  # editText { }
	}
	# button text="Continue" { }
}

screen App { }

Cross-Referencing concepts

commit 21caad3d1dce0446727f7fe15624f9529a76b0fd

Xtext provides a simple way to have cross-references in your language. For example, we want to set the Screen layout property pointing to a Layout declared elsewhere in the code.

Screen:
	'screen' name=ID
	'{'
		('show' layout=[Layout])?
	'}'
;

Example

application "My First App" => org.xtext.example.droid.myfirstapp

screen Welcome {
	show Default
}

layout Default {
	# textView "Welcome to First App" { }

	# layout Sublayout {
  	# textView "Put your name" { }
	  # editText { }
	}
	# button text="Continue" { }
}

screen App { }

Resource Concepts

commit c3e39d244f0a06a6e63d947d5b1e5a937899a530

Using the tools learned thus far, we'll introduce the Resource concept:

Application:
	'application' name=STRING
		'=>' packageName=PackageName
	(screens+=Screen | layouts+=Layout | resources+=Resource)+
;

Resource:
	StringResource | IntegerResource | BooleanResource
	| ColorResource | DimensionResource
	| ArrayResource | DrawableResource
;

StringResource:
	name=ID '=' value=STRING
;

IntegerResource:
	name=ID '=' value=INT
;

BooleanResource:
	name=ID '=' value=BOOL
;

ColorResource:
	name=ID '=' value=HEX_COLOR
;

DimensionResource:
	name=ID '=' value=DimensionValue
;

ArrayResource:
	IntegerArrayResource | StringArrayResource
;

IntegerArrayResource:
	name=ID '=' '['
		(items+=INT (',' items+=INT)* )
	']'
;

StringArrayResource:
	name=ID '=' '['
		(items+=STRING (',' items+=STRING)* )
	']'
;

DrawableResource:
	(BitmapDrawableResource | TransitionDrawableResource)
;

BitmapDrawableResource:
	name=ID '=' filename=ID
;

TransitionDrawableResource:
	name=ID
		from=[BitmapDrawableResource] '<->' to=[BitmapDrawableResource]
;

DimensionValue:
	FLOAT ('dp' | 'sp' | 'pt' | 'px' | 'mm' | 'in')
;

BOOL:
	'YES' | 'NO'
;

FLOAT:
	INT ('.' INT)?
;

terminal HEX_COLOR:
  '#'
  ('0'..'9'|'A'..'F'|'a'..'f') ('0'..'9'|'A'..'F'|'a'..'f')
  ('0'..'9'|'A'..'F'|'a'..'f') ('0'..'9'|'A'..'F'|'a'..'f')
  ('0'..'9'|'A'..'F'|'a'..'f') ('0'..'9'|'A'..'F'|'a'..'f')
  (('0'..'9'|'A'..'F'|'a'..'f') ('0'..'9'|'A'..'F'|'a'..'f'))? 
;

Example:

application "My First App" => org.xtext.example.droid.myfirstapp

title = "Welcome to First App"
name_label = "Put your name here"
button_label = "Continue"
default_number = 125
should_run = YES
max_width = 25 px
points_per_game = [
	12, 18, 9, 25, 14
]

screen Welcome {
	show Default
}

layout Default {
	# textView "Welcome to First App" { }

	# layout Sublayout {
  	# textView "Put your name" { }
	  # editText { }
	}
	# button text="Continue" { }
}

screen App { }

Note: the BOOL, FLOAT and HEX_COLOR rules will all generate string values. To generate, for example, boolean and float values you need to create a value converter to each rule (covered in the appendix).

The ValueAccess concept

commit 09bf777e8acea7360594e6a29a1cb062fbba930b

Now we'll create some rules that are not going to represent any concept from the domain. They will only help to ease the language specification.

The Android environment allows you to specify the Activity and View properties using both literal values and references to resources. So, we are going to create a concept to represent this value-access concept.

ValueAccess:
	(StringVA | IntegerVA | BooleanVA | AnyDrawableVA | DimensionVA)
	| ResourceAccess
;

AnyDrawableVA:
	DrawableVA | ColorVA
;

StringVA:
	StringRA | value=STRING
;

IntegerVA:
	IntegerRA | value=INT
;

BooleanVA:
	BooleanRA | value=BOOL
;

ColorVA:
	ColorRA | value=HEX_COLOR
;

DimensionVA:
	DimensionRA | value=DimensionValue
;

DrawableVA:
	DrawableRA
;

ResourceAccess:
	StringRA | IntegerRA | BooleanRA | ColorRA | DimensionRA | DrawableRA
;

StringRA:
	'string'
	(
	  ( '(' (package=PackageName '/')? resource=[StringResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

IntegerRA:
	'integer'
	(
	  ( '(' (package=PackageName '/')? resource=[IntegerResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

BooleanRA:
	'bool'
	(
	  ( '(' (package=PackageName '/')? resource=[BooleanResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

ColorRA:
	'color'
	(
	  ( '(' (package=PackageName '/')? resource=[ColorResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

DimensionRA:
	'dimen'
	(
	  ( '(' (package=PackageName '/')? resource=[DimensionResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

ArrayRA:
	'array'
	(
	  ( '(' (package=PackageName '/')? resource=[ArrayResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

DrawableRA:
	'drawable'
	(
	  ( '(' (package=PackageName '/')? resource=[DrawableResource] ')' )
	| ( '[' (package=PackageName '/')? externalResource=ID ']' )
	)
;

And now, we change the Widgets properties to use the ValueAccess to specify its values:

comit 36acef1c9cbd42797182a2a2c22f323ed1886f06

TextView:
	'textView' (name=ID)?
		text=StringVA
	'{'
	'}'
;

Button:
	'button' (name=ID)?
		( text=StringVA | src=AnyDrawableVA )
	'{'
	'}'
;

ImageView:
	'imageView' (name=ID)?
		src=DrawableVA
	'{'
	'}'
;

Spinner:
	'spinner' (name=ID)?
		prompt=StringVA
	'{'
	'}'
;

EditText:
	'editText' (name=ID)?
		text=StringVA?
	'{'
	'}'
;

Example:

application "My First App" => org.xtext.example.droid.myfirstapp

title = "Welcome to First App"
name_label = "Put your name here"
button_label = "Continue"

screen Welcome {
	show Default
}

layout Default {
	# textView string(name_label) { }
	# textView "Description" { }

	# layout Sublayout {
  		# textView string(name_label) { }
  		# editText string[external_text] { }
	}
	# button string(button_label) { }
}

Layout-related Properties

commit 2075220de651548dcf4050f6e531af88dcea65f5

Now that we have created the ValueAccess we can start adding some properties to the View (Layout and Widget). Let's start with the layout-related properties, and group them in a LayoutProperties concept.

First, create the concept:

LayoutProperties:
	'layout:'
	'{'
		//LinearLayoutParams
		('height:' layout_height=DimensionVA ';')?
		('width:' layout_width=DimensionVA ';')?
		('weight:' layout_weight=IntegerVA ';')?
		('marginBottom:' layout_marginBottom=DimensionVA ';')?
		('marginLeft:' layout_marginLeft=DimensionVA ';')?
		('marginRight:' layout_marginRight=DimensionVA ';')?
		('marginTop:' layout_marginTop=DimensionVA ';')?

		//RelativeLayoutParams
		('above:' layout_above=[View] ';')?
		('alignBaseline:' layout_alignBaseline=[View] ';')?
		('alignBottom:' layout_alignBottom=[View] ';')?
		('alignLeft:' layout_alignLeft=[View] ';')?
		('alignParentBottom:' layout_alignParentBottom=BooleanVA ';')?
		('alignParentLeft:' layout_alignParentLeft=BooleanVA ';')?
		('alignParentRight:' layout_alignParentRight=BooleanVA ';')?
		('alignParentTop:' layout_alignParentTop=BooleanVA ';')?
		('alignTop:' layout_alignTop=[View] ';')?
		('alignWithParentIfMissing:' layout_alignWithParentIfMissing=BooleanVA ';')?
		('below:' layout_below=[View] ';')?
		('centerHorizontal:' layout_centerHorizontal=BooleanVA ';')?
		('centerInParent:' layout_centerInParent=BooleanVA ';')?
		('centerVertical:' layout_centerVertical=BooleanVA ';')?
		('toLeftOf:' layout_toLeftOf=[View] ';')?
		('toRightOf:' layout_toRightOf=[View] ';')?
	'}'
;

And then, add it to the Layout and Widgets:

Layout:
	'layout' name=ID
	'{'
		(layoutProperties=LayoutProperties)?
		views=ViewCollection
	'}'
;

TextView:
	'textView' (name=ID)?
		text=StringVA
	'{'
		(layoutProperties=LayoutProperties)?
	'}'
;

Button:
	'button' (name=ID)?
		( text=StringVA | src=AnyDrawableVA )
	'{'
		(layoutProperties=LayoutProperties)?
	'}'
;

ImageView:
	'imageView' (name=ID)?
		src=AnyDrawableVA
	'{'
		(layoutProperties=LayoutProperties)?
	'}'
;

Spinner:
	'spinner' (name=ID)?
		prompt=StringVA
	'{'
		(layoutProperties=LayoutProperties)?
	'}'
;

EditText:
	'editText' (name=ID)?
		text=StringVA?
	'{'
		(layoutProperties=LayoutProperties)?
	'}'
;

Example

application "My First App" => org.xtext.example.droid.myfirstapp

title = "Welcome to First App"
name_label = "Put your name here"
button_label = "Continue"
left_margin = 10px

screen Welcome {
	show Default
}

layout Default {
	layout: {
		marginLeft: dimen(left_margin);
		marginRight: 5px;
		centerHorizontal: YES;
	}

	# textView string(name_label) { }
	# textView "Description" { }

	# layout Sublayout {
  		# textView string(name_label) { }
  		# editText string[external_text] { }
	}
	# button string(button_label) { }
}

But there are some problems with this implementation. It restricts the way we can specify the layout properties of a View: we can only define the properties following the order they were defined in the grammar file. That is a big problem, because the DSL users wil not even know about the grammar.

For instance, this example is not valid and lead to parse errors:

application "My First App" => org.xtext.example.droid.myfirstapp

layout Default {
	layout: {
		marginRight: 5px;
		marginLeft: 10px;
		centerHorizontal: YES;
	}

	# button "Go" { }
}

It happens because, in the example, the rule to the marginLeft feature is declared before the rule to the marginRight feature. So, once the parser reaches the rule to the marginRight it already left the marginLeft rule behind and it expects finding another rule that is after marginRight.

Despite the features of LayoutProperties are all optional, they are an ordered sequence of optional features.

A Partial solution

commit 09eec79e39d063e5b845e4478c37609279db644b

One solution could be making this sequence a loop of any LayoutProperty features.

LayoutProperties:
	'layout:'
	'{'
	 (
		//LinearLayoutParams
		('height:' layout_height=DimensionVA ';') |
		('width:' layout_width=DimensionVA ';') |
		('weight:' layout_weight=IntegerVA ';') |
		('marginBottom:' layout_marginBottom=DimensionVA ';') |
		('marginLeft:' layout_marginLeft=DimensionVA ';') |
		('marginRight:' layout_marginRight=DimensionVA ';') |
		('marginTop:' layout_marginTop=DimensionVA ';') |

		//RelativeLayoutParams
		('above:' layout_above=[View] ';') |
		('alignBaseline:' layout_alignBaseline=[View] ';') |
		('alignBottom:' layout_alignBottom=[View] ';') |
		('alignLeft:' layout_alignLeft=[View] ';') |
		('alignParentBottom:' layout_alignParentBottom=BooleanVA ';') |
		('alignParentLeft:' layout_alignParentLeft=BooleanVA ';') |
		('alignParentRight:' layout_alignParentRight=BooleanVA ';') |
		('alignParentTop:' layout_alignParentTop=BooleanVA ';') |
		('alignTop:' layout_alignTop=[View] ';') |
		('alignWithParentIfMissing:' layout_alignWithParentIfMissing=BooleanVA ';') |
		('below:' layout_below=[View] ';') |
		('centerHorizontal:' layout_centerHorizontal=BooleanVA ';') |
		('centerInParent:' layout_centerInParent=BooleanVA ';') |
		('centerVertical:' layout_centerVertical=BooleanVA ';') |
		('toLeftOf:' layout_toLeftOf=[View] ';') |
		('toRightOf:' layout_toRightOf=[View] ';')
	)+
	'}'
;

Now the previous example is going to work. But it leads to another problem: you are allowing duplicated feature declarations. Xtext is going to warn you about it, for example with this warn message: The assigned value of feature 'layout_height' will possibly override itself because it is used inside of a loop.

For instance, the following example is going to be valid. Note that the first centerHorizontal is an useless declaration and, for this reason, to repeat this declaration should be an invalid situation.

application "My First App" => org.xtext.example.droid.myfirstapp

layout Default {
	layout: {
		centerHorizontal: YES;
		marginRight: 5px;
		marginLeft: 7px;
		centerHorizontal: NO;
	}

	# button "Go" { }
}

So how can we solve the first problem without introducing the second one?

Unordered Groups

commit fd0037b794e25caca4260117d9c89fd5e66d49a1

What we need is to define an Unordered Group. From Xtext Manual:

The elements of an unordered group can occur in any order but each element must appear once. Unordered groups are separated by &.

LayoutProperties:
	'layout:'
	'{'
	 (
		//LinearLayoutParams
		('height:' layout_height=DimensionVA ';')?
		& ('width:' layout_width=DimensionVA ';')?
		& ('weight:' layout_weight=IntegerVA ';')?
		& ('marginBottom:' layout_marginBottom=DimensionVA ';')?
		& ('marginLeft:' layout_marginLeft=DimensionVA ';')?
		& ('marginRight:' layout_marginRight=DimensionVA ';')?
		& ('marginTop:' layout_marginTop=DimensionVA ';')?

		//RelativeLayoutParams
		& ('above:' layout_above=[View] ';')?
		& ('alignBaseline:' layout_alignBaseline=[View] ';')?
		& ('alignBottom:' layout_alignBottom=[View] ';')?
		& ('alignLeft:' layout_alignLeft=[View] ';')?
		& ('alignParentBottom:' layout_alignParentBottom=BooleanVA ';')?
		& ('alignParentLeft:' layout_alignParentLeft=BooleanVA ';')?
		& ('alignParentRight:' layout_alignParentRight=BooleanVA ';')?
		& ('alignParentTop:' layout_alignParentTop=BooleanVA ';')?
		& ('alignTop:' layout_alignTop=[View] ';')?
		& ('alignWithParentIfMissing:' layout_alignWithParentIfMissing=BooleanVA ';')?
		& ('below:' layout_below=[View] ';')?
		& ('centerHorizontal:' layout_centerHorizontal=BooleanVA ';')?
		& ('centerInParent:' layout_centerInParent=BooleanVA ';')?
		& ('centerVertical:' layout_centerVertical=BooleanVA ';')?
		& ('toLeftOf:' layout_toLeftOf=[View] ';')?
		& ('toRightOf:' layout_toRightOf=[View] ';')?
	)
	'}'
;

Note about Unordered Groups: The language artifacts generation involving unordered groups is a memory intensive task. You may experience a delay in the generation process.

Constant property values

commit ea1fe99b347cc0a1433ff29319d8a652bc0ac57d

The properties height and width accept also contant values (look at ViewGroup.LayoutParams). We can add this behaviour to our language by using Enum rules.

From Xtext manual:

Enum rules return enumeration literals from strings. They can be seen as a shortcut for data type rules with specific value converters. The main advantage of enum rules is their simplicity, type safety and therefore nice validation.

So, we are going to add a new ValueAccess to represent this kind of "Dimension OR Constant" value:

ValueAccess:
	(StringVA | IntegerVA | BooleanVA | AnyDrawableVA | DimensionVA | LayoutDimensionVA)
	| ResourceAccess
;

LayoutDimensionVA:
	DimensionVA | (const_value=LayoutDimensionKind)
;

enum LayoutDimensionKind:
	fill_parent | match_parent | wrap_content
;

And then, change the LayoutProperties:

LayoutProperties:
	'layout:'
	'{'
	 (
		//LinearLayoutParams
		('height:' layout_height=LayoutDimensionVA ';')?
		& ('width:' layout_width=LayoutDimensionVA ';')?
		& ('weight:' layout_weight=IntegerVA ';')?
		& ('marginBottom:' layout_marginBottom=DimensionVA ';')?
		& ('marginLeft:' layout_marginLeft=DimensionVA ';')?
		& ('marginRight:' layout_marginRight=DimensionVA ';')?
		& ('marginTop:' layout_marginTop=DimensionVA ';')?

		//RelativeLayoutParams
		& ('above:' layout_above=[View] ';')?
		& ('alignBaseline:' layout_alignBaseline=[View] ';')?
		& ('alignBottom:' layout_alignBottom=[View] ';')?
		& ('alignLeft:' layout_alignLeft=[View] ';')?
		& ('alignParentBottom:' layout_alignParentBottom=BooleanVA ';')?
		& ('alignParentLeft:' layout_alignParentLeft=BooleanVA ';')?
		& ('alignParentRight:' layout_alignParentRight=BooleanVA ';')?
		& ('alignParentTop:' layout_alignParentTop=BooleanVA ';')?
		& ('alignTop:' layout_alignTop=[View] ';')?
		& ('alignWithParentIfMissing:' layout_alignWithParentIfMissing=BooleanVA ';')?
		& ('below:' layout_below=[View] ';')?
		& ('centerHorizontal:' layout_centerHorizontal=BooleanVA ';')?
		& ('centerInParent:' layout_centerInParent=BooleanVA ';')?
		& ('centerVertical:' layout_centerVertical=BooleanVA ';')?
		& ('toLeftOf:' layout_toLeftOf=[View] ';')?
		& ('toRightOf:' layout_toRightOf=[View] ';')?
	)
	'}'
;

Adding more properties to the Layout

commit 4ddfd29b3748cf35d5aea8a7e32b047533eacd27

A Layout can be either a LinearLayout or RelativeLayout, so to simplify things we are going to use just a flag to indicate if it is relative or linear (the default layout being a linear one).

Layout:
	(isRelative?='relative')?
	'layout' name=ID
	'{'
	(
	  //View Properties
	  ('alpha:' alpha=FLOAT ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('minHeight:' minHeight=DimensionVA ';')?
	  & ('minWidth:' minWidth=DimensionVA ';')?
	  & ('nextFocusDown:' nextFocusDown=[View] ';')?
	  & ('nextFocusLeft:' nextFocusLeft=[View] ';')?
	  & ('nextFocusRight:' nextFocusRight=[View] ';')?
	  & ('nextFocusUp:' nextFocusUp=[View] ';')?
	  & ('padding:' padding=DimensionVA ';')?
	  & ('paddingBottom:' paddingBottom=DimensionVA';')?
	  & ('paddingLeft:' paddingLeft=DimensionVA';')?
	  & ('paddingRight:' paddingRight=DimensionVA';')?
	  & ('paddingTop:' paddingTop=DimensionVA';')?
	  & ('rotation:' rotation=DimensionVA';')?
	  & ('rotationX:' rotationX=DimensionVA';')?
	  & ('rotationY:' rotationY=DimensionVA';')?
	  & ('saveEnabled:' saveEnabled=BooleanVA';')?
	  & ('scaleX:' scaleX=DimensionVA';')?
	  & ('scaleY:' scaleY=DimensionVA';')?
	  & ('scrollX:' scrollX=DimensionVA';')?
	  & ('scrollY:' scrollY=DimensionVA';')?
	  & ('scrollbars:' scrollbars=BooleanVA';')?
	  & ('transformPivotX:' transformPivotX=DimensionVA';')?
	  & ('transformPivotY:' transformPivotY=DimensionVA';')?
	  & ('translationX:' translationX=DimensionVA';')?
	  & ('translationY:' translationY=DimensionVA';')?
	  & ('visibility:' visibility=LayoutVisibilityKind';')?

	  & (layoutProperties=LayoutProperties)?

	  //Layout Properties
	  & ('gravity:' gravity+=LayoutGravityKind ('|' gravity+=LayoutGravityKind)* ';')?
	  & ('orientation:' orientation=LayoutOrientationKind ';')?
	)

	views=ViewCollection
	'}'
;

enum LayoutVisibilityKind:
	visible | invisible | gone
;

enum LayoutGravityKind :
	top | bottom | left | right |
	center | center_vertical | center_horizontal |
	fill | fill_vertical | fill_horizontal |
	clip_vertical | clip_horizontal
;

enum LayoutOrientationKind:
	horizontal | vertical
;

Dealing with generation errors

Here we are using another unordered group (a big one). That is a useful (but expensive) feature. Your DSL artifacts is going to take some more time to be generated. At this point yout should start experiencing some unexpected errors when you try to generate the language artifacts.

The first one is something like this:

warning(205): ../org.xtext.example.droid/src-gen/org/xtext/example/droid/parser/antlr/internal/InternalDroid.g:1:8: ANTLR could not analyze this decision in rule Tokens; often this is because of recursive rule references visible from the left edge of alternatives. ANTLR will re-analyze the decision with a fixed lookahead of k=1. Consider using "options {k=1;}" for that decision and possibly adding a syntactic predicate.

This error is caused by a timeout in the ANTLR parser generator, and can be solved by changing your generation workflow to increase the timeout: Add this to your parser.antlr.XtextAntlrGeneratorFragment inside the GenerateDroid.mwe2 file:

	// The antlr parser generator fragment.
	fragment = parser.antlr.XtextAntlrGeneratorFragment {
	//  options = {
	//		backtrack = true
	//	}

		//http://www.antlr.org/pipermail/antlr-interest/2007-March/020014.html
		//http://20000frames.blogspot.com/2010/09/dealing-with-could-not-even-do-k1-for.html
		antlrParam = "-Xconversiontimeout" antlrParam = "10000"
	}

After this, you can start having java.lang.OutOfMemoryError: Java heap space errors. To solve this, you need to increase the allowed Java VM memory to the generator.

Right-click the GenerateDroid.mwe2, and select Run As > Run Configurations.... Under the Arguments tab place -Xmx512m in the VM Arguments text box.

Now you should be ready to start to working with this heavy DSL.

TODO: EXAMPLE IMAGE

Adding the widgets properties

20338cfc43934a3d72b1b131dc99ad794de215e5

TextView:
	'textView' (name=ID)?
		text=StringVA
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?

	  //TextView attributes
	  & ('autoLink:' autoLink=AutoLinkKind ';')?
	  & ('autoText:' autoText=BooleanVA ';')?
	  & ('capitalize:' capitalize=CapitalizeKind ';')?
	  & ('digits:' digits=StringVA ';')?
	  & ('editable:' editable=BooleanVA ';')?
	  & ('gravity:' gravity=LayoutGravityKind';')?
	  & ('hint:' hint=StringVA ';')?
	  & ('numeric:' numeric=BooleanVA ';')?
	  & ('password:' password=BooleanVA ';')?
	  & ('phoneNumber:' phoneNumber=BooleanVA ';')?
	  & ('singleLine:' singleLine=BooleanVA ';')?
	  & ('textColor:' textColor=ColorVA ';')?
	  & ('typeface:' typeface=TypefaceKind ';')?
	  & ('textSize:' textSize=DimensionVA ';')?
	  & ('textStyle:' textStyle+=TextStyleKind ('|' textStyle+=TextStyleKind)* ';')?		 
	)
	'}'
;

Button:
	'button' (name=ID)?
		( text=StringVA | src=AnyDrawableVA )
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?

	  // Specific properties
	  & ('hint:' hint=StringVA ';')?
	)
	'}'
;

ImageView:
	'imageView' (name=ID)?
		src=AnyDrawableVA
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?
	)
	'}'
;

Spinner:
	'spinner' (name=ID)?
		prompt=StringVA
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?
	)
	'}'
;

EditText:
	'editText' (name=ID)?
		text=StringVA?
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?

	  //TextView attributes
	  & ('autoLink:' autoLink=AutoLinkKind ';')?
	  & ('autoText:' autoText=BooleanVA ';')?
	  & ('capitalize:' capitalize=CapitalizeKind ';')?
	  & ('digits:' digits=StringVA ';')?
	  & ('editable:' editable=BooleanVA ';')?
	  & ('gravity:' gravity=LayoutGravityKind ';')?
	  & ('hint:' hint=StringVA ';')?
	  & ('numeric:' numeric=BooleanVA ';')?
	  & ('password:' password=BooleanVA ';')?
	  & ('phoneNumber:' phoneNumber=BooleanVA ';')?
	  & ('singleLine:' singleLine=BooleanVA ';')?
	  & ('textColor:' textColor=ColorVA';')?
	  & ('typeface:' typeface=TypefaceKind ';')?
	  & ('textSize:' textSize=DimensionVA ';')?
	  & ('textStyle:' textStyle+=TextStyleKind ('|' textStyle+=TextStyleKind)* ';')?
	)
	'}'
;

enum AutoLinkKind:
	none | web | email | phone | map | all
;

enum CapitalizeKind:
	none | sentences | words | characters
;

enum TypefaceKind:
	normal | sans | serif | monospace
;

enum TextStyleKind:
	normal | bold | italic
;

Polishing the DSL

3bad2f0a79484bcff429bfed4238b77fb26332cf

These are some changes with the intent of making the DSL a bit more useful.

Application:
	'application' name=STRING
		'=>' packageName=PackageName
		(
			('version:' versionCode=INT '=>' versionName=STRING)?
		  & (sdkVersion=ApplicationUsesSDK)?
		)
	(screens+=Screen | layouts+=Layout | resources+=Resource)+
;

ApplicationUsesSDK:
	'sdk:'
	'{'
	(
		('min:' minSdkVersion=INT ';')?
	  & ('max:' maxSdkVersion=INT ';')?
	  & ('target:' targetSdkVersion=INT ';')?
	)
	'}'
;

Screen:
	'screen' name=ID
	'{'
	(
		('show' layout=[Layout])
	  | widgets=ViewCollection
	)
	'}'
;

The Action concept

3d7336df27c3d4c82004e46ce487f42f1fae2348

Add the Action concept.

Action:
	(GoToURLAction | ShowLayoutAction | InvokeScreenAction)
;

GoToURLAction:
	'goTo' url=STRING
;

ShowLayoutAction:
	'show' layout=[Layout]
;

InvokeScreenAction:
	'invoke' activity=[Screen]
;

ButtonTarget returns InvokeScreenAction:
	'to' screen=[Screen]
;

Then, add the onClick property to the each View (Layout, and every Widget):

Layout:
	(isRelative?='relative')?
	'layout' name=ID
	'{'
	(
	  //View Properties
	  ('alpha:' alpha=FLOAT ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('minHeight:' minHeight=DimensionVA ';')?
	  & ('minWidth:' minWidth=DimensionVA ';')?
	  & ('nextFocusDown:' nextFocusDown=[View] ';')?
	  & ('nextFocusLeft:' nextFocusLeft=[View] ';')?
	  & ('nextFocusRight:' nextFocusRight=[View] ';')?
	  & ('nextFocusUp:' nextFocusUp=[View] ';')?
	  & ('onClick:' onClick=Action';')?
	  & ('padding:' padding=DimensionVA ';')?
	  & ('paddingBottom:' paddingBottom=DimensionVA ';')?
	  & ('paddingLeft:' paddingLeft=DimensionVA ';')?
	  & ('paddingRight:' paddingRight=DimensionVA ';')?
	  & ('paddingTop:' paddingTop=DimensionVA ';')?
	  & ('rotation:' rotation=DimensionVA ';')?
	  & ('rotationX:' rotationX=DimensionVA ';')?
	  & ('rotationY:' rotationY=DimensionVA ';')?
	  & ('saveEnabled:' saveEnabled=BooleanVA ';')?
	  & ('scaleX:' scaleX=DimensionVA ';')?
	  & ('scaleY:' scaleY=DimensionVA ';')?
	  & ('scrollX:' scrollX=DimensionVA ';')?
	  & ('scrollY:' scrollY=DimensionVA ';')?
	  & ('scrollbars:' scrollbars=BooleanVA ';')?
	  & ('transformPivotX:' transformPivotX=DimensionVA ';')?
	  & ('transformPivotY:' transformPivotY=DimensionVA ';')?
	  & ('translationX:' translationX=DimensionVA ';')?
	  & ('translationY:' translationY=DimensionVA ';')?
	  & ('visibility:' visibility=LayoutVisibilityKind ';')?

	  & (layoutProperties=LayoutProperties)?

	  //Layout Properties
	  & ('gravity:' gravity+=LayoutGravityKind ('|' gravity+=LayoutGravityKind)* ';')?
	  & ('orientation:' orientation=LayoutOrientationKind ';')?
	)
	views=ViewCollection
	'}'
;

TextView:
	'textView' (name=ID)?
		text=StringVA
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?

	  & ('onClick:' onClick=Action ';')?

	  //TextView attributes
	  & ('autoLink:' autoLink=AutoLinkKind ';')?
	  & ('autoText:' autoText=BooleanVA ';')?
	  & ('capitalize:' capitalize=CapitalizeKind ';')?
	  & ('digits:' digits=StringVA ';')?
	  & ('editable:' editable=BooleanVA ';')?
	  & ('gravity:' gravity=LayoutGravityKind';')?
	  & ('hint:' hint=StringVA ';')?
	  & ('numeric:' numeric=BooleanVA ';')?
	  & ('password:' password=BooleanVA ';')?
	  & ('phoneNumber:' phoneNumber=BooleanVA ';')?
	  & ('singleLine:' singleLine=BooleanVA ';')?
	  & ('textColor:' textColor=ColorVA ';')?
	  & ('typeface:' typeface=TypefaceKind ';')?
	  & ('textSize:' textSize=DimensionVA ';')?
	  & ('textStyle:' textStyle+=TextStyleKind ('|' textStyle+=TextStyleKind)* ';')?		 
	)
	'}'
;

Button:
	'button' (name=ID)?
		( text=StringVA | src=AnyDrawableVA )
		( onClick=ButtonTarget )?
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?

	  & ('onClick:' onClick=Action ';')?

	  // Specific properties
	  & ('hint:' hint=StringVA ';')?
	)
	'}'
;

ImageView:
	'imageView' (name=ID)?
		src=AnyDrawableVA
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?

	  & ('onClick:' onClick=Action ';')?
	)
	'}'
;

Spinner:
	'spinner' (name=ID)?
		prompt=StringVA
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?

	  & ('onClick:' onClick=Action ';')?
	)
	'}'
;

EditText:
	'editText' (name=ID)?
		text=StringVA?
	'{'
	(
		(layoutProperties=LayoutProperties)?
	  & ('top:' top=DimensionVA ';')?
	  & ('left:' left=DimensionVA ';')?
	  & ('width:' width=DimensionVA ';')?
	  & ('height:' height=DimensionVA ';')?
	  & ('background:' background=AnyDrawableVA ';')?
	  & ('clickable:' clickable=BooleanVA ';')?
	  & ('fadeScrollBars:' fadeScrollBars=BooleanVA ';')?
	  & ('isScrollContainer:' isScrollContainer=BooleanVA ';')?

	  & ('onClick:' onClick=Action ';')?

	  //TextView attributes
	  & ('autoLink:' autoLink=AutoLinkKind ';')?
	  & ('autoText:' autoText=BooleanVA ';')?
	  & ('capitalize:' capitalize=CapitalizeKind ';')?
	  & ('digits:' digits=StringVA ';')?
	  & ('editable:' editable=BooleanVA ';')?
	  & ('gravity:' gravity=LayoutGravityKind ';')?
	  & ('hint:' hint=StringVA ';')?
	  & ('numeric:' numeric=BooleanVA ';')?
	  & ('password:' password=BooleanVA ';')?
	  & ('phoneNumber:' phoneNumber=BooleanVA ';')?
	  & ('singleLine:' singleLine=BooleanVA ';')?
	  & ('textColor:' textColor=ColorVA';')?
	  & ('typeface:' typeface=TypefaceKind ';')?
	  & ('textSize:' textSize=DimensionVA ';')?
	  & ('textStyle:' textStyle+=TextStyleKind ('|' textStyle+=TextStyleKind)* ';')?
	)
	'}'
;

Exporting your DSL as an Eclipse Plugins

  1. File > Export ...
  2. Choose Plug-in Development > Deployable plug-ins and fragments
  3. Select the projects org.xtext.example.droid, org.xtext.example.droid.generator and org.xtext.example.droid.ui
  4. Choose a destination folder.

Instaling your generated plugin

  1. Copy the generated plugins (org.xtext.example.droid_1.0.0.jar, org.xtext.example.droid.generator_1.0.0.jar and org.xtext.example.droid.ui_1.0.0.jar) from the folder chosen in the previous step to the plugins folder (inside your Eclipse install).
  2. Restart Eclipse

Extracting your DSL metamodel to its own plugin

ea0002493f6f169a7e5321afe6776fad443dc114

This is an optional step and is useful, for example, if you want to develop several plugins and don't want they to be depending on Xtext. We are going to do it in order to use the same Ecore metamodel to the EEF.edit tutorial.

Creating a new EMF project:

  1. File > New > Project...
  2. Eclipse Modeling Framework > Empty EMF Project
  3. Project name: "org.emf.example.droid"
  4. Finish the wizard.

Moving the metamodel to its own plugin

Make sure you have generated the Xtext language artifacts (by running the MWE2 Workflow). It will update the generated Ecore metamodel.

  1. Locate the file Droid.ecore in the org.xtext.example.droid project (the Xtext project). It is located at the src-gen folder, inside the org.xtext.example.droid package.
  2. Move the file to the org.emf.example.droid project (the EMF project). Place it under the model folder.
  3. Create a new Genmodel file in the EMF project
  4. In the model folder, select the Droid.ecore file and go to menu File > New > Other...
  5. Eclipse Modeling Framework > EMF Generator Model
  6. Name the file Droid.genmodel and place it inside the model folder
  7. Select Ecore model
  8. Browse workspace and chose the Droid.ecore file.
  9. Open the created Droid.genmodel file.
  10. Select its root item Droid and click the menu Generator > Generate Model Code

Exporting and installing the generated EMF plugin

NOTE: To avoiding errors you must remove all the content from the src-gen folder in the org.xtext.example.droid project (the Xtext project) and regenerate the language artifacts.

  1. Right-click the EMF project (org.emf.example.droid) and select Export
  2. Choose Plug-in Development > Deployable plug-ins and fragments
  3. Choose a destination folder.
  4. Copy the generated plugin (org.emf.example.droid_1.0.0.jar) from the folder chosen in the previous step to the plugins folder (inside your Eclipse install).
  5. Restart Eclipse

Addind a dependency to the Xtext plugin

  1. Open the plugin.xml file inside your Xtext project (org.example.xtext.droid)

  2. Under the Dependencies tab, click Add to add a dependency to your Xtext plugin.

  3. Chose the EMF plugin org.emf.example.droid

  4. Select the added dependency and click Properties

  5. Check the Reexport this dependency option.

  6. Go to the Extensions tab and remove the org.eclipse.emf.ecore.generated_package extension.

  7. Go to the Runtime tab and remove the org.xtext.example.droid.droid, org.xtext.example.droid.droid.impl and org.xtext.example.droid.droid.util packages from the Exported Packages list.

  8. Open the GenerateDroid.mwe2 file

  9. Add the registerGeneratedEPackage = "droid.DroidPackage" to the bean = StandaloneSetup { ... } inside Workflow { ... }

  10. Open the Droid.xtext and change generate droid "http://www.xtext.org/example/droid/Droid" to import "http://www.xtext.org/example/droid/Droid"

  11. Regenerate the language artifacts and run the generated editor.