Eclipse, Xtext, Software Development, Language Engineering

New Project Wizard: einfach mit Xtext 2.14 generieren

Wer DSLs mit Eclipse Xtext umsetzt, profitiert davon, dass die notwendige Infrastruktur automatisiert aus der Grammatik erzeugt wird. Mit minimalem Aufwand entsteht somit eine Umgebung, in der alles gegeben ist, um direkt mit der eigenen DSL durchzustarten. Eine Grammatik und zwei Knöpfe später startet man voller Vorfreude Eclipse und fragt sich zunächst, welche Art von Projekt zu erstellen ist. Diese Frage beantwortet Xtext bisher nicht und der Entwickler hat die Wahl. 

Ist das Projekt erzeugt, stellt sich nun die Frage, wie die brandneue DSL genutzt werden kann... Wie war nochmal die Dateiendung? Ist das jetzt eine Textdatei? Gibt es keinen Wizard? Oh, ein Beispiel wäre super! Aber wo finde ich das? 

Wer kennt diese Situation nicht?

Bisher war das Erstellen eines Wizards Handarbeit – immer wieder. Dabei sind die Anforderungen an Wizards kein Hexenwerk und die Umsetzung auch nicht. Was man sich wünscht, ist ein Wizard der eindeutig zeigt, wie ein spezielles Projekt für unsere DSL zu erzeugen ist, das im besten Fall sogar bereits Beispiele enthält. Das lässt sich doch generieren, oder? Klar! Und in Xtext 2.14 haben wir genau diese Möglichkeit geschaffen.

Auch wenn diese Version erst mit Eclipse Photon im Juni 2018 erscheinen wird, möchten wir hier gerne schon mal einen kleinen Einblick ermöglichen.

Erzeugen des Wizards

Direkt nach dem Anlegen eines neuen Xtext-Projektes enthält das Projekt eine mwe2-Datei, die definiert, was generiert wird. Diese enthält den folgenden Abschnitt:

language = StandardLanguage {
   name = "org.xtext.example.mydsl.MyDsl"
   fileExtensions = "mydsl"
   serializer = {
       generateStub = false
   }
   validator = {
       // composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator"
   }
}


Um den Wizard zu generieren, ist dieser Abschnitt um ein TemplateProjectWizardFragment für den Wizard zu erweitern.

language = StandardLanguage {
   name = "org.xtext.example.mydsl.MyDsl"
   fileExtensions = "mydsl"
   serializer = {
       generateStub = false
   }
   validator = {
       // composedCheck = "org.eclipse.xtext.validation.NamesAreUniqueValidator"
   }
   projectWizard = {
       generate = true
   }
}


Wenn nun die Generierung wie üblich über Run As -> MWE2 Workflow angestoßen wird, dann werden nicht nur der Editor, die Autocompletion usw. generiert, sondern eben auch alle notwendigen Extensions für den Wizard.

Im UI-Projekt der Sprache wird ein Package wizard generiert, das vor allem eine Datei MyDslProjectTemplateProvider.xtend enthält (wobei der Name der Datei von dem Namen der Sprache abhängt). In dieser Datei wird der Inhalt des Wizards definiert. Weitere Konfigurationen sind normalerweise nicht nötig.

Wenn man nach der Generierung direkt den Runtime-Workspace öffnet, ist es möglich, neue Projekte anzulegen, indem der neue Wizard geöffnet wird – zum Beispiel über das Hauptmenü über File -> New -> Project. Hier gibt es eine Kategorie MyDsl mit einem MyDsl Project (wiederum vom Namen der Spache abhängig).
Sollten diese Namen unpassend sein, lässt sich das natürlich mit Standard-Eclipse-Mitteln in der plugin.xml anpassen. Für diejenigen, die den Wizard in ihre bestehende Sprache einbauen möchten und schon verzweifelt nach den neuen Einträgen in der plugin.xml suchen noch ein Tipp: Xtext generiert nach dem ersten Generatorlauf nicht mehr direkt in die plugin.xml sondern in die plugin.xml_gen und das Zusammenführen ist Handarbeit.

OpenNewProjectWizard


Auf der ersten Seite lässt sich wie üblich ein Projektname und eine Location festlegen.

NewProjectWizardSeite1


Die zweite Seite bietet eine Auswahl mit allen definierten Templates an, aus der der Benutzer auswählen kann. Per default wird lediglich ein “Hello World”-Template zur Verfügung gestellt.

NewProjectWizardTemplateSelection


Die folgende, optionale Seite bietet Möglichkeiten, das Template zu konfigurieren. Das ist im Falle des “Hello World”-Beispiels natürlich auch nur als solches zu verstehen.

NewProjectWizardTemplateConfiguration


Wird der Wizard mit “Finish” beendet, dann wird ein Projekt erzeugt, dem bereits die Xtext-Nature zugewiesen wurde. In diesem Projekt befindet sich dann eine DSL Datei mit beispielhaftem Inhalt.

GeneratedProject-1


Definieren eigener Templates

Um das “Hello World” an die eigene Sprache anzupassen oder weitere Templates hinzuzufügen, muss der TemplateProvider, eine Klasse welche IProjectTemplateProvider implementiert, angepasst werden. Dieser liefert mit der Methode getProjectTemplates eine Liste mit allen verfügbaren Templates für die Sprache zurück. Per default sieht die Implementierung wie folgt aus:

class MyDslProjectTemplateProvider implements IProjectTemplateProvider {
   override getProjectTemplates() {
       #[new HelloWorldProject]
   }
}


Das Hinzufügen eines weiteren Templates bedeutet also das Hinzufügen einer neuen Instanz zu dieser Liste. Zum Beispiel:

class MyDslProjectTemplateProvider implements IProjectTemplateProvider {
   override getProjectTemplates() {
       #[new HelloWorldProject, new TestProject]
   }
}


Das neue Projekt muss eine Subklasse von
AbstractProjectTemplate sein. Am einfachsten lässt sich eine solche durch die Nutzung der Active Annotation ProjectTemplate erzeugen. Diese Annotation bietet die Möglichkeit, den Namen, ein Icon sowie einen Beschreibungstext für das Template festzulegen. Diese regeln die Darstellung des Templates in der Liste der verfügbaren Templates im Wizard.

Man würde also in etwa so beginnen:

@ProjectTemplate(label="Test", icon="project_template.png",
   description="<p><b>Test</b></p><p>This is a test project.</p>")
final class HelloWorldProject {
}


Hier ist zumindest die Methode
generateProjects(IProjectGenerator) zu implementieren. Der übergebene IProjectGenerator enthält dabei eine einzelne Methode generate(ProjectFactory), welche man nun beliebig oft aufrufen kann, um beim Beenden des Wizards Eclipse-Projekte anzulegen. Dabei liefert Xtext bereits verschiedene Implementierungen der ProjectFactory mit, um Java-, Plugin- oder Featureprojekte zu erzeugen. Eigene Implementierungen sind hier natürlich denkbar.

Ein Aufruf, um ein einfaches Pluginprojekt mit einer Datei zu erzeugen, kann dann zum Beispiel so aussehen:

 generator.generate(new PluginProjectFactory => [
   projectName = projectInfo.projectName
   location = projectInfo.locationPath
   projectNatures += #[JavaCore.NATURE_ID,
                       "org.eclipse.pde.PluginNature",
                       XtextProjectHelper.NATURE_ID]
   builderIds += JavaCore.BUILDER_ID
   folders += "src"
   addFile('''src/Model.mydsl''', '''
       Hello World!
   ''')
])


Zusätzliche Aufrufe von
generate erzeugen weitere Projekte, während weitere Aufrufe von addFile zusätzliche Dateien in den Projekten erzeugen.

UI zur Parametrisierung

Um nicht nur komplett statische Templates zu liefern, ist es möglich, mit der API ein einfaches UI zu definieren. Dieses bekommt der Nutzer präsentiert und kann so Parameter des Templates festlegen.

Die API bietet dabei per default die Möglichkeit, Checkboxen, Textfelder und Comboboxen zu definieren. Um Übersicht zu schaffen, können diese noch mit Group-Elementen gruppiert werden.

Die Klasse AbstractProjectTemplate liefert hierfür entsprechende Methoden, mit denen das Interface definiert werden kann. Eine Checkbox wird durch den Aufruf von check erzeugt. Wenn diese nun einer Instanzvariablen zugewiesen wird, kann diese in der generateprojects-Methode genutzt werden, um den vom Benutzer ausgewählten Wert zu ermitteln. Zum Beispiel könnte mit folgendem Code eine Checkbox erzeugt werden, welche die Generierung eine Projektes nach Auswahl durch den Benutzer unterdrückt:

@ProjectTemplate(label="Test", icon="project_template.png",
   description="<p><b>Test</b></p><p>This is a test project.</p>")
final class TestProject {
   val generateHello = check("Generate Hello", true)

   override generateProjects(IProjectGenerator generator) {
       if (generateHello.value) {
           generator.generate(new PluginProjectFactory => [
               projectName = projectInfo.projectName
               location = projectInfo.locationPath
               projectNatures += #[JavaCore.NATURE_ID,
                                   "org.eclipse.pde.PluginNature",
                                   XtextProjectHelper.NATURE_ID]
               builderIds += JavaCore.BUILDER_ID
               folders += "src"
               addFile('''src/Model.mydsl''', '''
                   Hello World!
               ''')
           ])
       }
   }
}


Textfelder und Comboboxen lassen sich mit den Methoden
textund combo analog anlegen und verwenden.

Sollte ein noch ausgefeilterer Wizard benötigt werden, können die Methoden updateVariables und validate wie im "Hello World"-Beispiel überschrieben werden. Diese werden nach jeder User-Interaktion mit dem UI aufgerufen.

updateVariables kann verwendet werden, um Widgets zu manipulieren – zum Beispiel um bestimmte Element zu aktivieren oder deaktivieren, wenn der Benutzer einen Wert im Wizard verändert.

validate hingegen kann einen Status zurückliefern, um Eingabefehler vom User im Wizard als Fehlermeldung zu melden.

Templates per Plugin beisteuern

Die Project Templates werden über einen Extension Point registriert. Zunächst einmal bekommt man als DSL-Entwickler davon nichts mit. Interessant wird dies erst, wenn das eigene Plugin durch Dritte erweitert wird. Diese können dann einfach weitere Implementierungen von IProjectTemplateProvider beisteuern. Hier ist es nötig, die implementierende Klasse zu referenzieren und den Grammatiknamen, zu dem man contributet, anzugeben. Über diesen Namen wird entschieden, in welchem Wizard die Templates angezeigt werden. Das von Xtext generierte Beispiel sieht in der plugin.xml dann wie folgt aus.

<extension point="org.eclipse.xtext.ui.projectTemplate">
    <projectTemplateProvider
            class="org.xtext.example.mydsl.ui.wizard.MyDslProjectTemplateProvider"
            grammarName="org.xtext.example.mydsl.MyDsl">
    </projectTemplateProvider>
</extension>


Wer jetzt nicht mehr auf Eclipse Photon warten kann, dem sei die nächtliche Update-Site http://download.eclipse.org/modeling/tmf/xtext/updates/composite/latest/ ans Herz gelegt. Einfach das neueste und heißeste Xtext installieren und loslegen!

    
Über Arne Deutsch

Arne Deutsch arbeitet als IT-Berater bei der itemis AG in Bonn. Seine Schwerpunkte sind Language Engineering, Xtext und die Entwicklung von Tools für Eclipse.