Nachdem das Projekt im Eclipse angelegt wurde, kann die Entwicklung des Plugins beginnen. Die Implementierung eines Plugins gliedert sich prinzipiell immer in die folgenden Phasen:
Bestimmung der Aufgabe des Plugins.
Implementieren des com.semture.ecoube.plugin.Action-Interfaces.
Implementieren des com.semture.ecoube.plugin.ToolbarButton und/oder com.semture.ecoube.plugin.MenuItem-Interfaces.
Implementieren des com.semture.ecube.plugin.Plugin-Interfaces.
Deployment des Codes.
Funktionstest.
Abbildung 2 zeigt ein Klassenstrukturdiagramm des zu entwickelnden Plugins.
Wie aus diesen Vorüberlegungen ersichtlich wird, greift das Plugin auf Elemente der Cubetto-API zurück. Für die Implementierung eines Plugins ist zunächst das Paket com.semture.ecube.plugin interessant.
Der komplette Quellcode für das zu entwickelnde Plugin kann hier heruntergeladen werden. Gehen Sie wie folgt vor, um den Code in Ihre Entwicklungsumgebung einzubinden:
Entpacken Sie das Archiv in einem Ordner in Ihrem Eclipse-workspace.
Legen Sie ein Neues Projekt mit dem Namen „Plugin“ an.
Das entpackte Plugin-Projekt wird von Eclipse automatisch erkannt, wenn Sie den Dialog beenden.
Da der Verweis auf die ecube_classes.jar
noch
fehlerhaft ist, wird sich Ihr Projekt noch nicht compilieren lassen.
Editieren Sie dazu den Pfad dieser .jar Datei unter
Project->Properties->Java Build Path -> Libraries (vgl.
Abbildung 1). Danach wird das Projekt fehlerfrei compiliert.
Für dieses einführende Beispiel sollen die Namen aller selektierten Modellelemente in einem Fenster ausgegeben werden. Die Aktivierung des Plugins soll durch einen Button auf der Werkzeugleiste und einen Eintrag im Menü des Modelleditors erfolgen. Beim Klicken auf den Button bzw. das Menü soll ein Dialog geöffnet werden, welcher die Namen aller im Modelleditor selektierten Modellelemente anzeigt.
Eine Pluginaktion wird durch die Implementierung des Action-Interfaces
realisiert. Dieses Interface bietet den Einstiegspunkt für die
Implementierung des Plugininhalts. Das Action-Interface fordert wiederum die
Implementierung einer Reihe von Methoden. Zunächst ist die Aktion selbst zu
beschreiben. Dazu dienen die Methoden getName()
,
getToolTip()
und
getHelp()
. Die Funktionen liefern jeweils Strings
zurück, welche den Namen, das Tooltip sowie die Kurzhilfe zu dieser Aktion
liefern. Der Name der Aktion wird innerhalb des Werkzeugs zur Verwaltung der
Plugins verwendet. Das Tooltip wird eingeblendet, wenn der Mauscursor über
dem Menüpunkt oder über dem Button auf der Werkzeugleiste steht. Die
Kurzhilfe wird zukünftig in einem entsprechenden Hilfefenster angezeigt. Sie
sollte daher in Form eines HTML-formatierten Textes zurückgeliefert
werden.
Mit getKeyStroke()
wird das Tastenkürzel
definiert, welches mit dem der Aktion verknüpft werden soll. Dieses Kürzel
wird nur dann aktiv, wenn zu der Aktion auch ein entsprechender Menüpunkt
erzeugt wurde. In diesem Fall wird das Tastenkürzel auch in die Beschreibung
des Menüpunkts aufgenommen.
VORSICHT: Wird ein Tastenkürzel doppelt belegt, so wird immer nur das zuerst definierte Kürzel weiterverwendet. Damit kann eine erste Implementierung des Action-Interfaces erfolgen:
/** * @return * Liefert den Namen der Aktion. */ public String getName() { return "Namen ausgeben"; } /** * @return * Liefert eine Kurzbeschreibung der Aktion. */ public String getTooltip() { return "Gibt die Namen aller markierten Modellelemente aus."; } /** * @return * Hilefetext der Aktion. */ public String getHelp() { return "<html>Gibt die Namen aller markierten Modellelemente aus. " + "Die Aktion ist nur dann verfügbar, wenn mindestens ein Modellelement markiert ist.</html>"; } /** * @return * Liefert das Tastenkürzel der Aktion. */ public KeyStroke getKeyStroke() { return KeyStroke.getKeyStroke('N', InputEvent.ALT_MASK + InputEvent.CTRL_MASK); }
Der Konstruktor der Aktion sollte eine Referenz auf dasjenige Objekt erhalten und speichern, dessen Klasse das Plugin-Interface implementiert (s.u.). Diese Referenz kann später zum Datenaustausch zwischen beiden Objekten verwendet werden:
/** * Referenz auf die Pluginimlementierung. */ private MyPlugin plugin; /** * Defaultkonstruktor. * * @param plugin * Referenz auf das Plugin. */ public MyPluginAction(MyPlugin plugin) { this.plugin = plugin; }
Eine weitere Beschreibung der Aktion bezieht sich auf das
Transaktionskonzept des Cubetto Toolsets. Soll das Plugin innerhalb einer
Transaktion ausgeführt werden, muss die Methode
isTransactional()
den Wert true liefern. In diesem
Fall läuft das Plugin in seiner eigenen Transaktion ab. Wird die
run()
-Methode (siehe unten) fehlerfrei durchlaufen,
wird die Transaktion mit einem commit()
korrekt
abgeschlossen. Wird die run()
-Methode durch eine
Exception unterbrochen, so wird die aktuelle Transaktion zurückgerollt und
damit alle Aktionen, die durch das Plugin vorgenommen wurden,
verworfen.
WICHTIG: Alle Plugins, welche die bestehenden Modelldaten verändern,
sollten immer in einer eigenen Transaktion ausgeführt werden und damit in
isTransactional()
true liefern. Die Methode
getTransactionDescription()
gibt in diesem Fall
denjenigen String an, der nach dem Abschluss der Transaktion diese
Transaktion (genauer den Savepoint) beschreibt. Nur wenn das Plugin nicht in
einer Transaktion ausgeführt werden soll, darf die Methode
getTransactionDescription()
null liefern.
Da das zu implementierende Plugin nur lesend auf die Daten des Cubetto
Toolsets zugreift, kann auf die Verwendung des Transaktionskonzepts
verzichtet werden. Mit diesem Wissen werden die Methoden
isTransactional()
und
getTransactionDescription()
wie folgt
implementiert:
/** * @return * Liefert false. */ public boolean isTransactional() { return false; } /** * @return * Liefert einen Leerstring, da die Aktion nicht in einer Transaktion läuft. */ public String getTransactionDescription() { return ""; }
Die eigentliche Arbeit des Plugins wird in den Methoden
canRun()
und run()
implementiert. Die Methode canRun()
liefert true,
wenn das Plugin ausgeführt werden darf. In diesem Fall werden die
verknüpften Toolbar- und Menüeinträge aktiviert. Damit lässt sich die
Ausführbarkeit des Plugincodes von bestimmten Bedingungen wie beispielsweise
an eine bestimmte Markierung von Modellelementen im Modelleditor abhängig
machen. Die Methode run()
wird dann ausgeführt,
wenn der Benutzer auf den Toolbar- bzw. Menüeintrag klickt.
Die Ausführbarkeit soll immer dann gewährleistet werden, wenn mindestens ein Modellelement markiert ist. Die aktuelle Markierung von Modellelementen wird im SelectionManager hinterlegt. Dieser wird bei der Initialisierung direkt an das Plugin übergeben (s.u.). Der Zugriff auf den SelectionManager wird, durch die Referenz auf das Plugin-Interface wie folgt hergestellt:
/** * @return * Liefert true, wenn die Selektion von E³-Elementen nicht leer ist. */ public boolean canRun() { return !plugin.getSelectionManager().deriveE3Objects().isEmpty(); }
Im letzten Schritt muss noch die
run()
-Methode implementiert werden, welche die
eigentliche Aktion des Plugins beschreibt. Gemäß der Aufgabenbeschreibung
werden die selektierten Objekte als Strings in einem Dialogfeld ausgegeben.
Zur Ausgabe wird der ScriptHelper
verwendet, welcher
Fenster für einfache Ein- und Ausgaben für diesen Zweck zur Verfügung
stellt. Die Implementierung sieht wie folgt aus:
/** * Gibt die Daten der selektierten E³-Elemente in einem Dialogfeld aus. */ public void run() { // Ausgabe der Daten über den ScriptHelper unter Zuhilfenahme des NameResolvers ScriptHelper.showInformation( "Markierte Objekte", NameResolver.getNames(plugin.getSelectionManager().deriveE3Objects())); }
Im nächsten Schritt stellt sich die Frage, wie das Plugin vom
Modellierungswerkzeug aus angesprochen werden soll. Hier werden zwei
Möglichkeiten bereitgestellt: Erstens, das Einblenden eines Buttons auf der
Werkzeugleiste des Modelleditors und zweitens, das Einblenden eines
Menüpunkts im Plugin-Menü des Modelleditors. Es können selbstverständlich
auch beide Lösungen parallel implementiert werden. Beginnen wir mit dem
MenuItem-Interface. Ein Menüpunkt wird in Java allgemein durch seinen Namen,
das Tastenkürzel innerhalb des Menüs (Mnemonic; unterstrichener Buchstabe)
sowie das Bild des Menüpunkts definiert. Analog sind die Methoden
getText
(), getMnemonic()
und getIcon()
zu implementieren:
/** * @return * Liefert den Text des Menüpunkts. */ public String getText() { return "Namen anzeigen"; } /** * @return * Liefert das Tastenkürzel des Menüpunkts innerhalb des Menüs; */ public char getMnemonic() { return 'P'; } /** * @return * Liefert das Bild des Menüpunkts. */ public ImageIcon getIcon() { return new ImageIcon("Dateiname der Biddatei"); }
Zusätzlich gibt es im Cubetto Toolset das Konzept der Gruppe. Eine
Gruppe ist im Menü des Modelleditors eine Menge zusammengehörige Menüpunkte,
welche jeweils durch einen Separator (horizontale Linie) abgetrennt werden.
Diese Gruppe wird für den Menüpunkt mit Hilfe der Funktion getGroup
definiert. Zu beachten ist dabei, dass diese Gruppennummern möglicherweise
bereits von anderen Plugins verwendet werden und folglich das Menüelement
des eigenen Plugins in die gleiche Gruppe einsortiert wird, wie die
Menüpunkte eines anderen Plugins. Um die Position des Menüpunkts innerhalb
einer Gruppe festzulegen, wird die Funktion
getIndex()
verwendet:
/** * @return * Liefert die Gruppe des Menüpunkts. */ public int getGroup() { return 1; } /** * @return * Liefert den Index des Menüpunkts innerhalb dieser Gruppe. */ public int getIndex() { return 1; }
Die Menüpunkte werden im Modelleditor dann aufsteigend anhand ihrer Gruppenposition und innerhalb der Gruppe nach ihrem Index sortiert. VORSICHT: Werden für zwei Menüeinträge die Gruppennummer und der Index jeweils doppelt vergeben, so erscheint nur ein Menüpunkt im Plugin-Menü. WICHTIG: Es ist nicht notwendig, die Index- und Gruppennummern lückenlos zu vergeben. Verwenden Sie daher für Ihre eigene Nummerierung einen festgelegten Zahlenbereich (z. B. Gruppennummern >= 1000), der nur von Ihnen benutzt wird.
Zum Schluss wird noch die mit dem Menüpunkt verknüpfte Plugin-Aktion
benötigt, welche mit der Methode getAction()
zurückgeliefert wird (siehe oben). Es empfiehlt sich, eine Referenz auf
diese Aktion während der Initialisierung des Menüpunkts im Constructor zu
übernehmen und diese Referenz in dieser Methode zurückzuliefern:
/** * Referenz auf die verknüpfte Aktion. */ private Action action; /** * Initialisiert ein Objekt dieser Klasse und übernimmt die mit * dem Menüpunkt verknüpfte Atkion. * * @param action * Verknüpfte Aktion. */ public MyPluginMenuItem(Action action) { this.action = action; } /** * @return * Liefert die Aktion des Menüpunkts. */ public Action getAction() { return action; }
Die Implementierung eines Buttons auf der Werkzeugleiste
(ToolbarButton-Interface) erfolgt analog. Ein ToolbarButton ist durch sein
Bild spezifiziert, welches in der Methode getIcon()
zurückgeliefert wird. Zusätzlich gibt es auch für die ToolbarButtons das
gleiche Gruppenkonzept wie für Menüpunkte. Das dort Gesagte gilt
entsprechend, sodass die Methoden getGroup()
und
getIndex()
implementiert werden müssen. Wiederum
liefert die getAction()
-Methode die mit dem
ToolbarButton verknüpfte Aktion zurück, welche oben bereits beschrieben
wurde. Ein minimales Codebeispiel sieht dann wie folgt aus:
/** * Referenz auf die zugeordnete Aktion. */ private Action action; /** * Initialisiert ein Objekt dieser Klasse und übernimmt die mit * dem Button verknüpfte Atkion. * * @param action * Die zum Button gehörende Aktion. */ public MyPluginToolbarButton(Action action) { this.action = action; } /** * @return * Liefert das Bild des Menüpunkts. */ public ImageIcon getIcon() { return new ImageIcon("Dateiname der Biddatei"); } /** * @return * Liefert die Aktion zurück. */ public Action getAction() { return this.action; } /** * @return * Liefert die Gruppe in der Werkzeugleiste */ public int getGroup() { return 1; } /** * @return * Liefert den Index in der Werkzeugleiste */ public int getIndex() { return 1; }
Für die Einbettung des Plugins in das Cubetto Toolset muss im letzten
Schritt das Plugin-Interface implementiert werden. Die Methoden
getAuthor()
, getName()
,
getVersion()
und
getDescription()
sind selbsterklärend. Die
Implementierung liefert hier die jeweiligen Zeichenketten als Ergebnis
zurück.
/** * @return * Liefert die Versionsnummer des Plugins zurück. */ public String getVersion() { return "0.1"; } /** * @return * Liefert den Autor des Plugins zurück. */ public String getAuthor() { return "Max Mustermann"; } /** * @return * Liefert den Namen des Plugins zurück. */ public String getName() { return "Testplugin"; } /** * @return * Liefert die Beschreibung des Plugins zurück. */ public String getDescription() { return "Gibt die markierten E³-Objekte als Strings aus."; }
Weiterhin darf die Plugin-Implementierung einen parameterlosen
Konstruktor erhalten, welcher lokale Variablen initialisiert. Im Beispiel
wird der Konstruktor dazu verwendet, die Aktion, welche vom Plugin
ausgeführt werden soll, zu initialisierung. Alternativ kann diese
Initialisierung auch erst in der init()
-Methode
erfolgen:
/** * Die dem Plugin zugeordnete Aktion. */ private Action action; /** * Parameterlose Konstruktoren sind für die Initialisierung * des Plugins erlaubt. */ public MyPlugin() { action = new MyPluginAction(this); }
Im nächsten Schritt müssen die Menüpunkte und Toolbareinträge mit dem
Plugin verknüpft werden. Dazu dienen die Methoden
getMenuItems()
und
getToolbarButtons()
. Beide Methoden liefern jeweils
eine Menge von Menü- und Toolbareinträgen zurück. Die Sortierung der
Einträge innerhalb dieser Menge wird anschließend über die Methoden
getGroup()
und getIndex()
durchgeführt, welche das MenuItem-Interface und das ToolbarButton-Interface
bereitstellt (siehe oben):
/** * @return * Liefert den verknüpften Menüeintrag zurück. */ public Collection<? extends MenuItem> getMenuItems() { Collection<MenuItem> result = new ArrayList<MenuItem>(); result.add(new MyPluginMenuItem(action)); return result; } /** * @return * Liefert den verknüpften Toolbareintrag zurück. */ public Collection<? extends ToolbarButton> getToolbarButtons() { Collection<ToolbarButton> result = new ArrayList<ToolbarButton>(); result.add(new MyPluginToolbarButton(action)); return result; }
Zum Schluss müssen noch die Methoden init()
und selectionHasChanged()
implementiert werden. Die
init()
-Methode wird bei der Initialisierung des
Plugins aufgerufen und übergibt wichtige Parameter an das Plugin. Zu diesen
Parametern gehört die aktuelle Session, in der das Plugin läuft sowie, eine
Referenz auf das Hauptfenster sowie eine Referenz auf den SelectionManager,
welcher die Selektionen von Elementen im Modelleditor verwaltet.
Die Referenz auf die Session ist für transaktionale Plugins wichtig, beispielsweise um eine bestimmte Aktion in einer untergeordneten Transaktion auszuführen oder um eine Transaktion zurückzurollen. Die Referenz auf den MainFrame ist für Plugins mit einer Fenstersteuerung wichtig. Die übergebene Referenz sollte bei diesen Fenstern als parent übergeben werden. Damit lassen sich insbesondere modale Dialoge programmieren.
Für das hier vorgestellte Beispiel ist nur die Referenz auf den
SelectionManager
interessant, welche im Objekt der
Klasse MyPlugin
gespeichert und bei Bedarf mit der
Methode getSelectionManager()
ausgeliefert
wird:
/** * Definition einer lokalen Variable. */ private SelectionManager selectionManager; /** * Initialisiert das Plugin. * * @param session * Wird nicht ausgewertet. * @param mainFrame * Wird nicht ausgewertet. * @param selectionManager * Wird zwischengespeichert. */ public void init(Session session, JFrame mainFrame, SelectionManager selectionManager) { this.selectionManager = selectionManager; } /** * @return * Liefert den SelectionManager zurück. */ public SelectionManager getSelectionManager() { return selectionManager; }
Die letzte zu implementierende Methode ist
selectionHasChanged()
. Diese wird immer dann
aufgerufen, wenn sich die Markierung im Modelleditor verändert. Damit ist es
möglich, die Ausgaben eines Plugins direkt von einer Markierung im
Modelleditor abhängig zu machen bzw. auf eine Markierungsveränderung gezielt
zu reagieren. Im hier vorliegenden Anwendungsfall wird diese Methode nicht
benötigt. Sie kann leer bleiben:
/** * Wird bei einer Veränderung der Selektion im Modelleditor gerufen. * * @param oldSelection * Alte Selektion (wird nicht ausgewertet) * @param newSelection * Neue Selektion (wird nicht ausgewertet) */ public void selectionHasChanged(Collection oldSelection, Collection newSelection) { // Bleibt leer }
Um das Plugin zu aktivieren, muss es im Plugin-Verzeichnis des Cubetto
Toolset abgelegt werden
(Installationsverzeichnis/plugins
). Soll es für alle
Benutzer eines Rechners verfügbar sein, wird es im Plugin-Ordner der
Cubetto-Installation abgelegt. Soll es nur für einen Benutzer freigegeben
werden, so wird es in seinem Homeverzeichnis abgelegt
(Homeverzeichnis/cubetto.workspace/plugins
).
Mit Eclipse ist dieses Deployment über einen Export der kompilierten Klassen als .jar-File einfach möglich:
Das Testen des Plugins ist denkbar einfach. Das Cubetto-Toolset wird gestartet und die Menü- und Toolbarbutton-Einträge werden sichtbar. Beim Klicken auf einen dieser Einträge wird die Pluginaktion ausgeführt.