Eclipse mithilfe von Skriptsprachen erweitern

Eclipse EASE: Scripting für alle!

Christian Pontesegger

©Shutterstock.com/patpitchaya

Während in den letzten Jahren Skriptsprachen immer tiefer in die Domäne klassischer Programmiersprachen eindrangen, blieb die Eclipse-Plattform davon unberührt. Bisher – denn dies ändert sich nun dank des Projekts Eclipse Advanced Scripting Environment (EASE).

Seit vielen Jahren gibt es Bestrebungen, Eclipse um Skriptfähigkeiten zu erweitern. Mehrere Anläufe dazu verliefen mehr oder minder im Sande, ohne bleibende Spuren zu hinterlassen. Die Ursachen dafür mögen vielfältig sein: die Wahl der falschen Skriptsprache, zu starre Funktionalität oder mangelnde Integration in die Oberfläche. Vielleicht war auch einfach die Zeit noch nicht reif dafür. Doch mit der modellbasierten Oberfläche von Eclipse 4 bieten sich völlig neue Möglichkeiten, Skripte nahtlos in Anwendungen zu integrieren. EASE schickt sich an, Skripte nicht nur für einige seltene Automatisierungsaufgaben anzubieten, sondern Anwendungen dynamisch zu erweitern, ohne erkennbaren Unterschied zu nativem Code.Die OSGi-Technologie von Eclipse hilft Java-Programmierern, neue Funktionalität als Features anzubieten und in ihre Applikationen zu integrieren. Anwender können damit aus einem fast unerschöpflichen Pool von Erweiterungen wählen. Jedoch gibt es immer wieder Spezialaufgaben, die sich mit Bordmitteln nicht lösen lassen. Oft fehlt auch nur ein kleines Tool, um den täglichen Arbeitsablauf wesentlich zu erleichtern. EASE ermöglicht genau dies – und noch viel mehr. Aber der Reihe nach: Im Kern vereint EASE eine RCP-Applikation mit einem Skript-Interpreter innerhalb derselben JVM. Damit ist die Auswahl an passenden Sprachen zwar etwas eingeschränkt, dafür müssen aber keine externen Komponenten installiert und gewartet werden. Interessant an solchen Sprachen ist, dass sie allesamt Interaktionen mit nativen Java-Klassen erlauben. So erhält ein Skript automatisch Zugriff auf die gesamte verfügbare Funktionalität der RCP-Applikation. Im Folgenden werden wir die einzelnen Features näher beleuchten und uns immer tiefer in die Skriptwelt vortasten. Um den Beispielen zu folgen, installiert man EASE direkt von der Nightly-Build-Updateseite oder bezieht den aktuellen Quellcode aus dem offiziellen Git Repository.

Erste Schritte

Will man Skriptcode ausführen, benötigt man zuerst einen Interpreter. Diese sollten wie in Listing 1 ausschließlich über das ScriptService angefordert werden. Jeder Interpreter wird in einem eigenen Job ausgeführt und läuft daher gekapselt in einem separaten Thread. Damit können mehrere Skripte parallel verarbeitet werden, ohne sich gegenseitig zu beeinflussen. Der Input dazu kann aus verschiedensten Quellen stammen und wird dem Interpreter als Input Stream oder String übergeben. Mittels schedule() wird der Interpreter gestartet, führt den übergebenen Code aus und beendet sich schließlich selbst. Mittels eines Flags kann der Interpreter auch in einen interaktiven Modus gesetzt werden. In diesem Fall beendet er sich nicht selbst, sondern wartet auf weitere Skriptfragmente. Dabei bleibt der Scope erhalten. Am besten lässt sich dies in der Script Shell ausprobieren (Abb. 1), die zum Experimentieren mit dem voreingestellten JavaScript-Interpreter einlädt. Das Resultat des letzten Kommandos wird direkt in der Shell dargestellt, während Output und Error Stream in eine Console umgeleitet werden. Hat man die ersten erfolgreichen Kommandos abgesetzt, kann man diese mittels des Makro-Rekorders aufzeichnen, speichern und bei Bedarf wieder abspielen. Der Rekorder lässt sich über die Toolbar starten, die aufgezeichneten Makros einfach per Doppelklick aus der Shell-Sidebar.

IScriptService service = (IScriptService) PlatformUI.getWorkbench().getService(IScriptService.class);
IScriptEngine engine = service.createEngine("JavaScript");

// run in interactive mode
// engine.setTerminateOnIdle(false);

engine.executeAsync("print('hello world');");
engine.schedule();

Abb. 1: Interaktive Script Shell

Neben dem interaktiven Modus können Skripte auch als Dateien im Workspace abgelegt werden. Von dort können sie mittels „Launch Configuration/Quicklaunch“ gestartet werden. Die Shell versteht sich auf die Ausführung solcher Dateien mittels Drag and Drop.
Mit der Grundfunktionalität von JavaScript alleine lässt sich kein produktiver Code schreiben. Neben Conditionals (if/switch) hat man noch Schleifen und ein paar mathematische Grundfunktionen zur Verfügung. Daher wird man schnell gezwungen, native Java-Klassen einzubinden. Diese lassen sich mittels new Packages.full.qualified.name.MyClass() instanziieren. Man kann sie danach als Variable abspeichern und/oder direkt auf die implementierten Methoden zugreifen. Packt man nützliche Codefragmente in Funktionen, lässt sich auf diese Weise bereits eine kleine Bibliothek aufbauen (Listing 2).
Einige grundlegende Funktionen bringt der Interpreter bereits mit: print(), eval(), exit(), include(). Letzteres Kommando erlaubt etwa, weitere Quelldateien einzubinden. Die inkludierte Datei wird dabei nicht nur geladen, sondern direkt ausgeführt. Dabei bleibt der Kontext der aufrufenden Datei erhalten und wird gegebenenfalls durch das include modifiziert. Neben absoluten und relativen Pfaden können auch URIs verwendet werden, darunter zwei eigens für includes konzipierte: workspace://<project name>/… und project://folder/script. Im Eclipse-Hilfesystem finden sich unter SCRIPTING GUIDE | LOADABLE MODULES detaillierte Informationen zu den einzelnen Befehlen.

function runProcess(executable, parameter) {
  var builder = new Packages.java.lang.ProcessBuilder();
  builder.command(executable, parameter);
  return builder.start();
}

var process = runProcess("explorer.exe", "C:userdata");

Schleichen sich in Skripten die ersten Fehler ein, entsteht schnell der Wunsch nach einem Debugger. Dieser kann wie gewohnt über DEBUG AS… aufgerufen werden. In der Implementierung für JavaScript lassen sich nicht nur die neuen Sprachfeatures wie include nutzen, sondern auch verwendete Java-Instanzen in der Variables-View betrachten und auswerten.

Eigene DOMs

Obwohl Scripting in der bisher beschriebenen Form schon den Zugriff auf alle verfügbaren Eclipse-APIs ermöglicht, ist die Syntax doch recht umständlich und mangels Code Completion nicht sehr komfortabel. EASE bietet deshalb Möglichkeiten, häufig benutzte Kommandos populärer in der Skriptsprache zu verankern. Ähnlich wie sich Eclipse mittels Plug-ins erweitern lässt, kann auch ein Skript-Interpreter zur Laufzeit neue Funktionalität nachladen. Dazu werden Methoden einer Java-Instanz als primitive Funktionen in der Skriptsprache abgebildet (Wrapping). Ein solches Modul wird bereits automatisch beim Start eines Interpreters geladen: Die zuvor erwähnten, erweiterten Befehle werden dynamisch aus einer Java-Instanz generiert. Neben den schon beschriebenen gibt es noch ein ganz spezielles solches Kommando: loadModule(„<name>“). Es erlaubt, zu einem beliebigen Zeitpunkt neue Funktionen nachzuladen. In der Script Shell lässt sich dies über den Toolbar-Eintrag „Load an external module“ ausprobieren.Module sind im Kern einfache Java-Klassen. Um eine public-Methode im Skript verfügbar zu machen, wird sie mittels @WrapToScript-Annotation markiert. Danach erfolgt eine Registrierung im plugin.xml mittels Extension Point. Beim Laden des Moduls wird dynamisch Code in der Zielsprache erzeugt und im Interpreter ausgeführt. Das bedeutet, dass eventuell bereits vorhandene Funktionen desselben Namens überschrieben werden können. Ebenfalls lässt sich eine geladene Methode nachträglich neu definieren. Neben Methoden kann dieser Mechanismus auch auf statische Felder angewendet werden. Listings 3 und 4 zeigen ein Beispiel für ein einfaches Modul.

public class ProcessModule {

   @WrapToScript
  public Process runProcess(String executable, String parameter) throws IOException {
    ProcessBuilder builder = new ProcessBuilder();
    builder.command(executable, parameter);
    return builder.start();
  }
}
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
  <extension
    point="org.eclipse.ease.modules">
    <module
      class="example.ProcessModule"
      name="Process"
      visible="true">
    </module>
  </extension>

</plugin>

In manchen Fällen ist es schlicht unmöglich, eine Klasse nachträglich mit Annotationen zu erweitern, etwa weil sie Teil einer Bibliothek ist oder die Lizenz es nicht gestattet. Registriert man eine solche Klasse als Modul, werden alle öffentlichen Methoden im Interpreter geladen. Dies geschieht generell, wenn die Klasse keine einzige @WrapToScript-Annotation enthält.Selbst ohne registrierten Extension Point lässt sich eine Instanz mittels des wrap(<instance>)-Befehls laden. Beispielsweise bindet ein

wrap(org.eclipse.core.resources.ResourcesPlugin.getWorkspace().getRoot()) 

den Workspace in ein Skript ein. Befehle wie getProject() sind dann unmittelbar nutzbar. Damit Anwender Module sinnvoll nutzen können, sind sie auf Dokumentation angewiesen. EASE bietet hierfür einen automatischen Generator an, der JavaDoc-Kommentare aus Modulen ausliest und daraus Eclipse-Hilfedateien erstellt. Als Entwickler installiert man die „EASE Developer Tools“ (beziehbar von der Updatesite) und wählt danach aus dem entsprechenden Plug-in-Kontextmenü CONFIGURE | ADD EASE DOCUMENTATION BUILDER. Die erstellten Hilfekapitel sind in der Anwendung unter SCRIPTING GUIDE | LOADABLE MODULES zu finden.

UI-Integration

Haben wir unsere ersten Skripte erstellt, steht als Nächstes die Einbindung derselben in das UI an. EASE überwacht dazu den Workspace und sucht nach gekennzeichneten Projekten. Darin werden alle Skripte auf spezielle Header überprüft, die sich in einem Kommentarblock am Beginn der Datei befinden müssen. Dort enthaltene Schlüsselwörter (Kasten: „Header-Metadaten“) werden benutzt, um ein Skript im UI darzustellen. „Menu“ etwa definiert den Eintrag in der Script Explorer View (Abb. 2), unter der das Skript dargestellt wird.

Header-Metadaten
• Menu: Definiert den Eintrag für das Skript im Script Explorer. Hierarchien werden mit ‚>‘ abgebildet.
• Description: Legt das Tooltip für obige View fest.
• Thread: UI: Führt das Skript im UI-Thread aus.
VisibleWhen: Expression, um das Skript in Kontextmenüs einzublenden.

Abb. 2: „Script Explorer“ mit eingebundenen Skripten

Da Skripte grundsätzlich in einem eigenen Thread laufen, wird jeder direkte UI-Zugriff mit einer SWTException „invalid thread access“ quittiert. Dies kann vermieden werden, indem die Skriptausführung mittels Thread: UI direkt in den UI-Thread verlagert wird. Dauert die Ausführung allerdings länger, kann damit der komplette UI-Thread in Mitleidenschaft gezogen werden. Obiges Feature ist also mit Vorsicht zu behandeln. Die bessere Alternative ist meist, eigene Module zur Verfügung zu stellen, die die UI-Zugriffe kapseln und damit den UI-Thread nur minimal belasten. Aus selbigem Grund sollten Sie übrigens niemals ein UI-Skript über den Debugger starten!Auf der 3.x-Plattform können Skripte per Script Explorer, über die Shell oder das Eclipse-Launching-Framework gestartet werden. In Eclipse-4-Applikationen mit ihrer modellbasierten Oberfläche lassen sich Skripte zudem in beliebige Kontextmenüs einbinden. Das Schlüsselwort VisibleWhen im Header wertet dazu eine Expression aus, deren Syntax an das Eclipse-Expressions-Framework angelehnt ist. Die Syntax erschließt sich am besten durch ein paar Beispiele, die sich über den NEW WIZARD (EXAMPLES | INTERPRETER | EXAMPLES) installieren lassen.

Eines davon, „ConvertToScriptingProject“, registriert beliebige Workspace-Projekte als Skript-Container. Gestartet werden kann es aus dem Kontextmenü eines jeden Eclipse-Projekts. Im Sourcecode finden wir zum einen den VisibleWhen-Ausdruck, zum anderen Beispielcode, um die aktuelle Selektion mittels Iterator zu konsumieren. Wenden wir dies auf unser bisheriges Explorer-Skript an, so können wir uns leicht ein Explore from here-Kontextmenü bauen (Listing 5). Dabei binden wir das Skript an IContainer-Objekte. Damit ist der Eintrag sowohl bei Projekten als auch bei Ordnern sichtbar. Beim Auslesen des Containers hilft getIterableSelection(), welches die aktuelle Selektion als Liste zurückliefert. Schließlich starten wir den Explorer über unser zuvor erstelltes Modul. Stattdessen hätten wir natürlich auch die Funktion aus Listing 2 verwenden können. Letzten Endes reichen zwanzig Zeilen Skriptcode, um Eclipse um ein neues Feature zu erweitern.

/**
 * Menu: Explore from here
 * Description : {Open an explorer at the selected resource.}
 * VisibleWhen:[With selection {
 *    Iterable {
 *        InstanceOf "org.eclipse.core.resources.IContainer"
 *    }
 *}]
 */

loadModule("SelectionModule");
var selection = getIterableSelection();
var container = selection.iterator().next();

if (container instanceof Packages.org.eclipse.core.resources.IContainer) {
  loadModule("Process");
  runProcess("explorer.exe", container.getLocationURI());
}

Weitere Sprachen

Bisher haben wir uns nur auf JavaScript als Skriptsprache fokussiert. Das Framework selbst ist allerdings generisch und erlaubt die Anbindung beliebiger weiterer Sprachen. So sind die Arbeiten an Jython etwa bereits so weit fortgeschritten, dass es produktiv genutzt werden kann. Aus lizenzrechtlichen Gründen darf der Interpreter allerdings nicht direkt mit EASE ausgeliefert werden. Installiert werden kann er über den Menüpunkt HELP | INSTALL EASE ADDITIONAL COMPONENTS. Wie JavaScript unterstützt auch Jython Module und die dazugehörigen Wrapper-Mechanismen. Damit können bestehende Module direkt in beiden Sprachen genutzt werden.
Weitere Sprachen sollen folgen. Derzeit gibt es bereits experimentelle Implementierungen für Groovy und JRuby. Bis zur Freigabe sind allerdings auch hier noch einige rechtliche Fragen zu klären.

Ausblick

Ganz oben auf der Wunschliste steht eine verbesserte UI-Integration. Skripte sollen integraler Bestandteil der Oberfläche werden und sich nahtlos in Menüs und Toolbars einfügen. Benutzer sollen so keinen Bruch zwischen nativem Code und Skripten erleben. Auch müssen die Editoren benutzerfreundlicher werden, indem sie kontextsensitive Hilfe und Code Completion anbieten.
Die Skripte selbst sollen in Zukunft aus verschiedensten Quellen stammen können. Statt sie in lokalen Projekten zu hosten, könnten Skripte auf einem HTTP-Server liegen oder im Unternehmen über ein Netzlaufwerk an Mitarbeiter verteilt werden. Metadaten sollen in solchen Fällen lokal adaptierbar sein, am besten mithilfe von grafischen Editoren.
Scripting ist typischerweise eine Domäne von Powerusern und daher oftmals nur einem kleinen Kreis zugänglich. Mit EASE wollen wir den Spagat schaffen, Einsteigern einfache Module an die Hand zu geben, während wir für versierte Benutzer die volle Komplexität von Eclipse verfügbar halten. Der Erfolg wird maßgeblich davon abhängen, ob wir eine ausreichend große Entwicklerschar dafür begeistern können. Denn neben einfacher Handhabung müssen Skripte vor allem einen echten Mehrwert bieten.
EASE ist ein sehr junges Projekt, das sich anschickt, einen alten Wunsch der Entwicklergemeinde umzusetzen. Jetzt daran teilzunehmen bedeutet, etwas Pioniergeist mitbringen zu müssen. Dafür erhält man die Möglichkeit, aktiv in die Gestaltung einzugreifen – egal ob man Skripte schreibt, Module definiert oder direkt an der Basis arbeitet. Erste Anlaufstelle für Interessenten sollte das Wiki sein, bei Problemen findet man Hilfe im Forum (Eclipse Projects/Eclipse 4) oder auf der Developer-Mailingliste.

Aufmacherbild: Man write a computer source code von Shutterstock / Urheberrecht: patpitchaya

Geschrieben von
Christian Pontesegger
Christian Pontesegger
Christian Pontesegger ist Gründer und Committer des EASE-Projekts und arbeitet als Eclipse-Entwickler für Infineon Technologies. Sein Blog ist unter http://codeandme.blogspot.com zu erreichen.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: