JPF Tutorial (Erläuterung der Demo-Anwendung)

Einführung

Dieses Tutorial ist eine detaillierte Beschreibung einer JPF Demo Applikation (JPF-Demo, Download), um Entwicklern einen schnellen Einstieg in JPF zu geben.
Bitte beachten: Dies ist kein Java Swing Tutorial. Dieses Tutorial beschreibt nur ein mögliches Beispiel wie man JPF einsetzen kann und berücksichtigt nicht alle möglichen Szenarien.

Es wird empfohlen den JPF-Demo Quellcode herunterzuladen und aus ihm ein neues Projekt in Ihrer Lieblings-Entwicklungsumgebung zu erstellen. Die Detaillierte Anleitung beschreibt, wie man JPF basierte Anwendungen in verschiedenen Java Entwicklungsumgebungen erstellt (im Augenblick nur für Eclipse).

JPF-Demo ist eine GUI-Applikation welche mit dem "Toolbox" Metaphor Entwurfsmuster im Hinterkopf entwickelt wurde. Das Hauptfenster ist eine Art Container für Tools - die eigentliche Funktionlität der Anwendung wird dabei in den Plug-ins oder Plug-in-Sets umgesetzt.

JPF-Demo - Code Colorer Tool

Auf dem Screen Shot können sie ein "Code Colorer Tool" sehen - das Programm nimmt Java Quellcode und wandelt ihn in HTML Text um, inklusive Syntax Highlighting. Zum Syntax Highlighting wird die Open-Source Bibliothek Java2Html (GPL, Java2Html Homepage) genutzt.

Anwendungsstruktur

Die Dateisystemstruktur dieser Anwendung sieht so aus:

[APPLICATION_HOME_FOLDER]/
 +- data/
 +- lib/
 |   +- commons-logging.jar
 |   +- jpf.jar
 |   +- jpf-boot.jar
 |   +- jpf-tools.jar
 |   +- jxp.jar
 |   +- log4j.jar
 +- logs/
 +- plugins/
 +- boot.properties
 +- log4j.properties
 +- run.bat
 +- run.sh


Hier die Erläuterungen:

data
In diesem Ordner können Plug-ins ihre Konfigurations- und andere Dateien ablegen.
lib
Hier liegen für den Start der Anwendung benötigte Bibliotheken wie z.B.  die JPF-Bibliotheken, ebenso wie die Bibliotheken für Logging. Logging-Bibliotheken werden auch von JPF selbst benötigt.
logs
Log-Dateien landen hier.
plugins
Hier befinden sich die JPF-Plugins, sozusagen ist dieser Ordner deren Lager.
boot.properties
Die Boot Konfigurationsdatei.
run.*
Start-Skripte der Anwendung.

Die Komponentenstruktur der Anwendung wird auf dem folgenden Diagramm dargestellt.

JPF-Demo Application Diagram

Start der Anwendung

Um die Anwendung zu starten macht das run-Skript einen Aufruf der main-Methode org.java.plugin.boot.Boot.main(String[]) der JPF-Boot Bibliothek. Diese Main-Methode liest die Konfigurationsdatei boot.properties aus, initialisiert das JPF Framework und läd alle Plugins aus dem plugins-Ordner. Am Ende ruft sie das org.jpf.demo.toolbox.core Plug-in auf, da wir dieses in der Konfigurationsdatei boot.properties spezifiziert haben.

Ab dem Zeitpunkt an dem JPF das Core Plug-in aufgerufen hat geht die gesamte Kontrolle der Anwendung an das Plug-in org.jpf.demo.toolbox.core über, welches wir wie in Eclipse "application plug-in" nennen. Die Plug-in Klasse org.jpf.demo.toolbox.core erbt von der speziellen abstrakten Klasse org.java.plugin.boot.ApplicationPlugin der JPF Boot-Bibliothek. Somit ist es möglich, dass der JPF Boot-Code unseren eigenen speziellen Boot-Code ausführt.

Core Plug-in

Wie so ziemlich jedes JPF Plug-in, besteht auch org.jpf.demo.toolbox.core aus zwei Teilen: Der Manifest-Datei und dem Plug-in Java Code. Wir werden uns die beiden der Reihe nach ansehen.

Plug-in manifest

Die Plug-in Manifest Datei ist eine XML Datei welche die Plug-in DTD (siehe z.B.plugin_1_0.dtd) erfüllen muss (zu XML und DTD siehe W3C oder W3Schools). Das Wurzel-Tag dieser XML-Datei ist:

<plugin id="org.jpf.demo.toolbox.core" version="0.0.4" class="org.jpf.demo.toolbox.core.CorePlugin">

Hier legen wir fest, dass die Plug-in ID "org.jpf.demo.toolbox.core" ist, und dass die Versionsnummer bei "0.0.4" liegt. Wir deklarieren außerdem, dass unser Plug-in eine "Plug-in Klasse" besitzt, nämlich org.jpf.demo.toolbox.core.CorePlugin, damit das JPF Framework unser Plug-in ordnungsgemäß initialisieren kann. Die "Plug-in Klasse" ist ein optionales Element in der Plug-in Deklaration und nur nötig, wenn während des Startes etwas in der doStart()- oder der doStop()-Methode aus dem Plugin-Interface ausgeführt werden soll. Lassen wir diese Deklarierung weg, also wenn während der Plug-in Aktivierung/Deaktivierung nichts spezielles gemacht werden muss, benutzt JPF eine Standard Plug-in Klasse. Beim Core Plug-in ist dies jedoch nicht der Fall, denn dieses besondere Plug-in ist der Anwendungs-Einstiegspunkt und muss bei seiner Aktivierung die GUI aufbauen und verwalten.

Das nächste Element im Manifest ist die Deklarierung der Bibliotheken:

<runtime>
	<library id="core" path="classes/" type="code">
		<export prefix="*"/>
	</library>
	<library type="resources" path="icons/" id="icons">
		<export prefix="*"/>
	</library>
</runtime>

Hier definieren wir, dass alle Java .class-Dateien dieses Plug-ins im Ordner "classes/" innerhalb des zugehörigen Plug-in Ordners abgelegt werden. Außerdem definieren wir, dass alle Klassen und Pakete (*) sichtbar für andere Plug-ins sind, und diese unseren Code frei verwenden können. Ebenso gibt es einen Ressources-Ordner "icons/", welcher ebenfalls für andere Plug-ins sicht- und benutzbar ist.

Der letzte Teil des Manifests ist der interessanteste und auch der mächtigste von JPF (ebenso wie von Eclipse), denn er macht unsere Anwendung extrem erweiterbar. Dieses ist die Deklaration eines Extension-Points:

<extension-point id="Tool">
	<parameter-def id="class"/>
	<parameter-def id="name"/>
	<parameter-def id="description" multiplicity="none-or-one"/>
	<parameter-def id="icon" multiplicity="none-or-one"/>
</extension-point>

Hiermit definieren wir, dass unser Core Plug-in einen Punkt veröffentlicht, an dem es durch andere Plug-ins erweitert werden kann. Wir nennen diesen Punkt "Tool" und erklären, dass Erweiterungen an diesem Punkt als "Tab" in der GUI dargestellt werden. Außerdem sollte jedes Plug-in das an diesem Punkt festmacht verschiedene Parameter zur Verfügung stellen, welche teils in der GUI benutzt werden, teils dazu um mit dem Plug-in zu kommunizieren (bsp. eindeutiger Namen). Für diesen Extension-Point definieren wir vier Parameter:

class
Dies ist ein benötigter Parameter vom Typ String, er sollte den kompletten Java Klassennamen enthalten.
name
Der Name des Tools, er wird als Tabname in der GUI zu sehen sein.
description
Die Tool-Beschreibung, welche als "Tab-Hint" in der GUI gezeigt werden wird. Dies ist ein optionaler Parameter.
icon
Der Dateiname des Tool-Icons. Dies ist ein optionaler Parameter.

Jetzt sind wir bereit um die Logik für unser Core Plug-in zu implementieren.


Plug-in code

Wir erinnern uns, dass wir im Plug-in Manifest deklariert haben, dass wir eine Plug-in Klasse org.jpf.demo.toolbox.core.CorePlugin bereitstellen werden. Normalerweise müssen wir dafür von der abstrakten Klasse org.java.plugin.Plugin erben und zwei Metohden implementieren die das JPF Framework während des Plug-in Lebenszyklus aufrufen wird: protected void doStart() throws Exception; und protected void doStop() throws Exception;. Aber in diesem Fall müssen wir von der Klasse org.java.plugin.boot.ApplicationPlugin erben, da wir das "Application Plug-in" entwickeln. Unsere Implementierung dieser beiden Methoden aus org.java.plugin.Plugin wird allerdings leer sein. Der wahre Grund dieser Plug-in Klasse ist die "Entry Point" Methode aus org.java.plugin.boot.ApplicationPlugin zur Verfügung zu stellen, welche von der JPF Boot-Library gerufen wird und anschließend den ganzen Zauber für uns erledigt.

Die Hauptaufgabe unserer Core Plug-in Klasse ist die GUI zu erstellen und zu verwalten. Außerdem wollen wir hier den Code für die Realisierung des Extension-Points implementieren, den wir im Manifest definiert haben. Der Trick besteht hier darin, die GUI-Logik effizient zu organisieren. Das Prinzip dahinter ist, Plug-ins erst so spät wie möglich zu aktivieren und so viele Informationen wie möglich aus der  Extension Deklaration zu sammeln. Deswegen haben wir auch so viele Parameter in der Extension-Point Deklaration definiert. Wir bauen die GUI als ein "Set von Tabs mit später Initialisierung von Komponenten". Werfen Sie einen Blick auf den JPF-Demo Quellcode für weitere Details. Der interessanteste Punkt ist die Kommunikation mit unserem Plug-in Framework um alle Extensions zu erhalten welche mit unserem Extension-Point verbunden sind:

ExtensionPoint toolExtPoint =
	getManager().getRegistry().getExtensionPoint(
		getDescriptor().getId(), "Tool");
for (Iterator it = toolExtPoint.getConnectedExtensions()
		.iterator(); it.hasNext();) {
	Extension ext = (Extension) it.next();
	JPanel panel = new JPanel();
	panel.putClientProperty("extension", ext);
	Parameter descrParam = ext.getParameter("description");
	Parameter iconParam = ext.getParameter("icon");
	URL iconUrl = null;
	if (iconParam != null) {
		iconUrl = getManager().getPluginClassLoader(
			ext.getDeclaringPluginDescriptor())
				.getResource(iconParam.valueAsString());
	}
	tabbedPane.addTab(
		ext.getParameter("name").valueAsString(),
		(iconUrl != null) ? new ImageIcon(iconUrl) : null,
		panel, (descrParam != null) ?
			descrParam.valueAsString() : "");
}


Der nächste interessante Punkt ist die Vereinbarung, die wir für unsere Extension Klasse  vorgenommen haben. Wir sagen hier, dass der "class" Parameter welcher in der Extension Deklaration definiert wurde auf eine Klasse verweisen soll, welche das Interfaceorg.jpf.demo.toolbox.core.Tool implementiert. Wir erklären außerdem, dass Objekte dieser Klasse mit dem Default-Konstruktor instanziiert werden. Wir versprechen auch, dass die Methode init einmal im Lebenszyklus der Extension aufgerufen wird. Es folgt der Code, der das eben umrissene Prinzip in die Tat umsetzt:

// Activate plug-in that declares extension.
getManager().activatePlugin(
		ext.getDeclaringPluginDescriptor().getId());
// Get plug-in class loader.
ClassLoader classLoader = getManager().getPluginClassLoader(
		ext.getDeclaringPluginDescriptor());
// Load Tool class.
Class toolCls = classLoader.loadClass(
		ext.getParameter("class").valueAsString());
// Create Tool instance.
tool = (Tool) toolCls.newInstance();
// Initialize class instance according to interface contract.
tool.init(toolComponent);

Von diesem Punkt an können wir unsere Anwendung veröffentlichen und warten, dass jemand ein Plug-in dafür schreibt :) Da wir aber nicht den ganzen Tag Zeit haben, machen wir das selbst und entwickeln verschiedene Plug-ins, welche wir dann zu unserer "Tool Box" hinzufügen.

Code Colorer Plug-in

In dieser Sektion werde ich im Detail erklären wie man ein Plug-in erstellt und damit dann ein Tool zu unserer Tool Box hinzufügt. Wie Sie bereits wissen, müssen wir dafür eine Extension für unseren Extension-Point "Tool" implementieren, welchen wir im Plug-in "org.jpf.demo.toolbox.core" definiert haben. Wie zuvor teilen wir die Erklärung in zwei Teile auf, die Manifest Beschreibung und in Kommentare zum Plug-in Code.

Plug-in Manifest

Das Wurzel-Tag der Manifest XML Datei sollte Ihnen bereits bekannt vorkommen:

<plugin id="org.jpf.demo.toolbox.codecolorer" version="0.0.5">

Wie Sie sehen ist die Plug-in ID "org.jpf.demo.toolbox.codecolorer", während die Plug-in Klasse nicht mehr deklariert wird da wir während des Starts/Stops des Plug-ins keinerlei Code auszuführen haben.

Der nächste Abschnitt im Manifest ist neu für uns:

<requires>
	<import plugin-id="org.jpf.demo.toolbox.core"/>
</requires>

Wir definieren hier, dass unser Plug-in von "org.jpf.demo.toolbox.core" abhängt. Unser Plug-in benötigt also womöglich Methoden und Ressourcen des Core Plug-ins, oder stellt womöglich eine Erweiterung für Extension-Points dar, die im Core Plug-in definiert wurden.

Die Bibliotheken Deklaration ist hier etwas aufwendiger, da wir beabsichtigen Third-Party Bibliotheken zu benutzen, nebst unserem eigenen Code.

<runtime>
	<library id="codecolorer" path="classes/" type="code"/>
	<library id="java2html" path="lib/java2html.jar"
		type="code">
		<doc caption="Java2html Library by Markus Gebhard">
			<doc-ref path="docs/java2html"
				caption="java2html library"/>
		</doc>
	</library>
	<library type="resources" path="icons/" id="icons"/>
</runtime>
	

Wie Sie sehen haben wir hier die Code Library "java2html" definiert, welche auf die JAR Datei "lib/java2html.jar" verweist, außerdem haben wir eine Referenz auf die Dokumentation dieser Bibliothek gesetzt (dies ist zwar nur ein Beispiel, aber es ist guter Stil zu jedem Plug-in Manifest Element Dokumentation zur Verfügung zu stellen). Beachten Sie auch, dass wir in diesem Plug-in Manifest keinerlei Code oder Ressourcen exportieren, da wir davon ausgehen den Code dieses Plug-ins nur innerhalb dieses Plug-ins zu verwenden.

Das letzte Element des Manifests definiert eine Extension, keinen Extension-Point. Diese Extension ist der Zweck unseres Plug-ins.

<extension plugin-id="org.jpf.demo.toolbox.core"
	point-id="Tool" id="codeColorerTool">
	<parameter id="class"
		value="org.jpf.demo.toolbox.codecolorer.CCTool"/>
	<parameter id="name" value="Code Colorer Tool"/>
	<parameter id="description"
		value="Tool to colorize source code text"/>
	<parameter id="icon" value="codecolorer.png"/>
</extension>

Wie Sie sehen können, geben wir unserer Extension die ID "codeColorerTool" und spezifizieren als Extension-Klasse "org.jpf.demo.toolbox.codecolorer.CCTool". Darunter sehen Sie, dass diese Klasse den Vertrag für den Extension-Point "Tool" vollständig erfüllt, da es zu den beiden Pflichtparametern und sogar zu den beiden optionalen  Parameter Werte spezifiziert. Jetzt kann das JPF Framework automatisch einen Integritätscheck auf unserem Plug-in ausführen und uns warnen, wenn etwas in unseren Deklarationen daneben ging.


Plug-in Code

Der Code-Teil des "org.jpf.demo.toolbox.codecolorer" Plug-ins besteht aus zwei Klassen. Die Klasse org.jpf.demo.toolbox.codecolorer.CCTool implementiert das Interface org.jpf.demo.toolbox.core.Tool und hält sich damit an den Vertrag für den Extension-Point "Tool". Die Methode init dieses Interfaces erzeugt in unserem Fall einfach nur die Tool-GUI und fügt es zu einem gegebenen Swing Container als Kind hinzu. Die Klasse org.jpf.demo.toolbox.codecolorer.CodeColorer ist die interne Plug-in Klasse die die eigentliche Arbeit übernimmt. Der Code dieser Klasse ist von der Java2Html Klasse de.java2html.Java2HtmlApplication übernommen, mit kleineren nicht so gravierenden Modifikationen. Ich werde den Code hier aber nicht weiter kommentieren, wer es genau wissen will sei auf den JPF-Demo Quellcode und dieJava2Html Homepage verwiesen.

Beachten Sie wie leicht es war, einfach Tools als Plug-in zu unserer Toolbox hinzuzufügen! Der größte Teil der Plug-in Logik ist Toollogik, und nicht Plug-in Verwaltungslogik. JPF und unser Core Plug-in haben uns diese Arbeit abgenommen.


Andere Plug-ins

Es sind noch zwei weitere Plug-ins in der JPF-Demo Anwendung. Das erste ist ein Plug-in Browser Tool.

JPF-Demo - Plug-in Browser Tool

Dieses Plug-in erlaubt es uns eine beliebige Anzahl anderer Plug-ins zu laden und dann deren Struktur zu untersuchen, ebenso wie ihre Abhängigkeiten. Beachten Sie, dass jedes der Plug-ins welches sie mit dem Browser Tool laden mit einer eigenen Browser Tool Instanz der Plug-in Registry geladen wird (eine eigene Registry für das Browser Tool, in der die geladenen Plug-ins dann eingetragen werden). Von der Demo Anwendung werden diese Plug-ins nicht aktiviert, sie sind noch nicht einmal sichtbar für die Demo Anwendung, da sie ja bei einer anderen Plug-in Registry registriert sind. Die Hauptaufgabe dieses Plug-ins liegt darin, wie man Plug-ins in JPF benutzen kann, und dem Anwender einen rudimentären Überblick über die Plug-in Struktur zu geben.

Ein anderes Plug-in ist das Database Browser Tool.

JPF-Demo - Database Browser Tool

Es ist nicht die Aufgabe diese Plug-ins zu zeigen wie man mit JDBC in Java arbeitet, sondern zu zeigen wie man Erweiterungsfähigkeit zu einer Java Anwendung hinzufügen kann. Tatsächlich ist der "DB Browser" nicht nur ein Plug-in, sondern ein Set von Plug-ins. Zunächst implementiert "org.jpf.demo.toolbox.dbbrowser" das Interface "Tool" damit es am "Tool" Extension-Point andocken und die DB Browser GUI verwalten kann. Als nächstes definiert dieses Plug-in seinen eigenen Extension-Point "Database" und eröffnet anderen Plug-ins damit die Möglichkeit am DB Browser anzudocken. Das Plug-in "org.jpf.demo.toolbox.dbbrowser" weiß eigentlich gar nichts über spezielle Datenbanken. Alle Datenbankspezifischen Dinge werden durch den Extension-Point "Database" (und einige Interfaces) abstrahiert und dann in anderen Plug-ins implementiert. Wer es genau wissen möchte ist im Quellcode des Plug-ins gut aufgehoben. In der Manifest Datei ist darauf zu achten, dass der Extension-Point direkt vor der Extension definiert wird, da das Manifest sonst den Integritätstest nicht übersteht.

Was kommt als nächstes?

Ich hoffe dieser Artikel konnte Ihnen ein Grundverständnis der Prinzipien welche JPF und JPF-Anwendungen formen, geben. Jetzt können Sie versuchen diese auf Ihre eigenen Aufgaben anzuwenden oder auch ganz eigene Ansätze in der Entwicklung mit JPF zu entwickeln.

Fühlen Sie sich eingeladen Ihre Fragen auf public JPF forum zu stellen. Sie sind natürlich auch eingeladen hier Ihre Ideen und Benutzungsmethoden zu teilen und zu diskutieren. Dies wird auf jeden Fall dazu beitragen JPF zu verbessern und es als Werkzeug, mit dem sich extrem flexible Anwendungen bauen lassen, bekannt zu machen.