BeanDev: Extensionpoints für Actions
Der Begriff Extension Point wird in der Eclipse Welt so inflationär verwendet, dass man meinen könnte in der NetBeans Platform würde so etwas nicht existieren. Dabei ist das nicht richtig, im Gegenteil. Es gibt so viele Möglichkeiten, dass man als Modulentwickler schon mal etwas überfordert ist.
Möchte man selber Extension Points anbieten, muss man sich sowieso zwangsläufig damit beschäftigen. Aber es ist einfacher als man denkt. Der Modul-Assistent legt bei Wunsch schon eine deklarative Schnittstelle an, in der man eigene Objekte global registrieren kann. Die globalen Bekanntmachungen können von anderen Modulen aufgegriffen, erweitert und sogar ausgeblendet werden. Umgekehrt habe ich aus meinem Modul die Möglichkeit auch anderen Modulen in die Registrierung einzugreifen (Menüs ausblenden oder neue hinzufügen, Tastenkürzel anpassen, Toolbars verändern, Paletten anreichern, Lokalisierungen vornehmen, usw usf.).
Hier geht es aber mal erstmal darum selber Extension-Points anzubieten.
Wie schon gesagt, auf Wunsch wird eine XML-Datei zum Modul erzeugt und diese XML-Datei als deklarativer Ansprungspunkt registriert. Die Info, dass es eine (sogenannte) Layer Datei gibt, erfolgt in der MANIFEST.MF:
OpenIDE-Module-Layer: org/sepix/tutorialbuilder/layer.xml
Damit muss eine Datei namens layer.xml im Paket org.sepix.tutorialbuilder existieren. Diese Datei enthält zunächst nur den Struktureintrag <filesystem></filesystem>.
In meinem (gar nicht so fiktiven) Modul habe ich ein Icon in der Statusleiste registriert und möchte dort per Mausklick ein Popup-Menu darstellen. Das möchte ich selbst einfach aufbauen können und auch anderen Entwicklern die Möglichkeit bieten dieses Menü zu erweitern/verändern.
Ich füge dafür folgende Einträge zwischen <filesystem></filesystem> ein:
<folder name="org-sepix-tutbuilder">
<folder name="Menu">
</folder>
</folder>
Es bietet sich immer an, für ein Modul als Root-Folder den eigenen Paket-Namen zu verwenden. Ansonsten kommt es ggf. zu unerwünschten Überdeckungen in der globalen Registry. Schließlich werden alle Layer-Dateien aller aktiven Module zu einem großen globalen Lookup zusammengestellt.
Irgendwo in meinem Modul gibt es nun einen MouseListener, der darauf wartet, dass der Anwender das Statusleisten-Icon anklickt. Dann baue ich eine Popup-Menü zusammen:
JPopupMenu menu = new JPopupMenu();
Lookup lookup = Lookups.forPath("org-sepix-tutbuilder/Menu");
for (Action a : lookup.lookupAll (Action.class)) {
menu.add(new JMenuItem (a));
}
Der Aufruf Lookups.forPath("org-sepix-tutbuilder/Menu") liefert mir umgehend ein Lookup (das ist ein "Korb" von Objekten) auf mein Menu-Folder. Mit lookupAll (Action.class) will ich aus dem "Korb" nun alle Objekte mit der Super-Klasse javax.swing.Action. Aus den Actions bastele ich mit Menü-Einträge und füge diese dem Popup-Menü hinzu.
Wenn ich nicht nur Actions in dem Folder habe, sondern auch andere Objekte, sieht das nur unwesentlich komplizierter aus:
JPopupMenu menu = new JPopupMenu();
Lookup lookup = Lookups.forPath("org-sepix-tutbuilder/Menu");
Collection c = lookup.lookupAll (Object.class);
for (Object next : c) {
if (next instanceof Action) {
JMenuItem item = new JMenuItem ((Action)next);
menu.add(item);
} else if (next instanceof JSeparator) {
JSeparator item = (JSeparator)next;
menu.add(item);
}
}
Mit lookupAll (Object.class) muss ich mich dann aber selber darum kümmern, welche Objekte mir in der Collection angeboten werden.
Wenn ich jetzt mein Modul teste, wird das Popup-Menü etwas traurig sein. Kein Wunder. In meiner layer.xml sind ja auch keine Actions vorhanden. Die werden da auch nicht direkt eingetragen. In der NetBeans Platform werden alle Actions zentral registriert. Dazu nutze ich den "New Action" Assistenten. Dieser erzeugt mir ebenfalls in meiner layer.xml einige Einträge und eine ActionListener-Klasse:
<folder name="Actions">
<folder name="Help">
<file name="org-sepix-tutorialbuilder-ui-ScreenShotAction.instance">
<attr name="SystemFileSystem.localizingBundle" stringvalue="org.sepix.tutorialbuilder.Bundle"/>
<attr name="delegate" newvalue="org.sepix.tutorialbuilder.ui.ScreenShotAction"/>
<attr name="displayName" bundlevalue="org.sepix.tutorialbuilder.ui.Bundle#CTL_ScreenShotAction"/>
<attr name="iconBase" stringvalue="org/sepix/tutorialbuilder/res/statusicon.gif"/>
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.alwaysEnabled"/>
<attr name="noIconInMenu" boolvalue="false"/>
</file>
</folder>
</folder>
Das Attribut "delegate" verweist übrigens auf die reale Klasse ScreenShotAction die aber nichts anderes als ein Object implements ActionListener ist (also in Wirklichkeit keine javax.swing.Action).
Wie komme ich aber an meine Action ran, die ich dem Popup-Menü hinzufügen will? Durch Magie. Oder besser gesagt, der automatischen Erstellung von Actions aus Layer.xml-Einträgen durch die NetBeans Platform. Alle global registrierten Actions werden nämlich auf Wunsch in echte Action-Objekte ins Leben gerufen. Das geht aber nur, wenn man sie eben in dem Root-Folder "Actions" einträgt und diese Bekanntmachungen als symbolische Links an anderen Stellen "überträgt". Damit kommen wir zu den shadow-Links.
Also, der "New Action"-Assistent erzeugt mir eine globale Registrierung einer Klasse, die nichts anderes ist als ein ActionListener. Diese Klasse soll aber in meinem Popup-Menü liegen. Also füge ich in meinem Menü einen Link zu dieser globalen Registrierung ein:
<folder name="org-sepix-tutbuilder">
<folder name="Menu">
<file name="org-sepix-tutorialbuilder-ui-ScreenShotAction.shadow">
<attr name="originalFile" stringvalue="Actions/Help/org-sepix-tutorialbuilder-ui-ScreenShotAction.instance"/>
</file>
</folder>
</folder>
Und (taaaraaa) nun habe ich in meinem Popup-Menü auch eine echte Action liegen (nicht nur einen ActionListener), weil NetBeans bei shadow-Anforderungen automatisch ein Wrapper-Objekt erzeugt (was als Superklasse eine javax.swing.Action ist) und ActionEvents an den ActionListener delegiert.
Das sieht ungeheuer kompliziert aus, um ein Popup aufzubauen. Aber doch auch schlüssig. Jetzt gibt es eine zentrale Deklaration von einer Action. Der Anwender findet diese in den Optionen zur Keymap wieder und darf beliebige Tastenkürzel hinzufügen. Andere Modulprogrammierer können ebenfalls diese Action manipulieren (sogar austauschen). Zusätzlich habe ich mit meinem Extension-Point eine Möglichkeit geboten Menüeinträge hinzufügen zu lassen.
Denn jetzt kann ein anderes Modul in "seiner" layer.xml folgendes machen:
<folder name="org-sepix-tutbuilder">
<folder name="Menu">
<file name="other-company-ui-GalleryAction.shadow">
<attr name="originalFile" stringvalue="Actions/Window/other-company-ui-GalleryAction.instance"/>
</file>
</folder>
</folder>
Damit ist in meinem Popup-Menü ein neuer Eintrag hinzugefügt worden. Mein Modul wurde also erweitert. Und nur so können eigene Entwicklungen in dem Ökosystem NetBeans Platform bestehen: Nicht durch Abkapselung, sondern durch Offenheit und Extension Points. Man kann selber die eigenen Module besser erweitern und fremde Entwickler fügen Funktionen hinzu, an die man selber vielleicht nie gedacht hatte.
Beste Grüße,
Josch.
Da werden Sie geholfen:
Das deutsche NetBeans Forum
![Validate my RSS feed [Valid RSS]](http://www.sepix.de/fileadmin/valid-rss.png)