Ein Werkzeug zur interaktiven Bearbeitung von

Und-Oder-Bäumen


von Matthias Nickles



Inhalt


Einführung in die Theorie

Entwicklung-Tools und Build-Vorgang

Übersicht über die Programmstruktur

Die Klasse Node

Textuelle Repräsentation und Parser AOTParse

Die Klasse View

Die Klasse Document

Die Klassen AOT und ApplicationFrame

Kommunikation mit dem Host per CATTP


Quellcode:

- Hierarchie aller Klassen

- Alle Methoden und Attribute

Categories API:

- Hierarchie öffentlicher (public) Klassen

- Öffentliche (public) Methoden und Attribute


Bekannte Softwarefehler

Literatur



Entwicklungs-Tools und Build-Vorgang

Categories wurde als Java-Applet entwickelt (Entwickler: Matthias Nickles (eMail: nicklesm@usa.net)). Verwendet wurde dazu hauptsächlich Microsoft J++ 1.0.6xxx unter Windows 95, sowie (zur Anpassung an verschiedene Unix-Derivate) das JDK 1.0.2 von Sun. Obwohl sich der Quellcode prinzipiell mit jeder Java-Version compilieren lassen sollte, sollte nur ein Java 1.0.x -kompatibler Interpreter bzw. Just-in-time Compiler zur Ausführung verwendet werden. Da die Graphikausgabe und das Event- Handling auf dem AWT von Java 1.0 beruhen, welches gegenüber Java 1.1 Unterschiede aufweist, kann die uneingeschränkte Lauffähigkeit unter Java 1.1 nicht garantiert werden. Weiterhin ist zu beachten, daß das Programm nur mit den GUIs (von) Win32 und Motif getestet wurde; weil das AWT 1.0 leider an verschiedenen Stellen Abhängigkeiten vom Betriebssystem und dessen Graphikoberfläche aufweist, sind für andere Betriebssysteme bzw. GUIs sehr wahrscheinlich Anpassungen auf Quellcode-Ebene notwendig. Bereiche im Quellcode, die direkt auf solche Eigenheiten bezugnehmen oder einen der vielen Fehler von Java 1.0.x betreffen, sind mit drei Sternchen (***) gekennzeichnet.

Bevor Categories übersetzt werden kann, ist es notwendig, den Quellcode für einige Klassen automatisch generieren zu lassen. Es handelt sich dabei um die Klassen, die den Parser für Baum-Quelltexte bilden sowie die Klassen der Properties-Dialogbox (hier kann der notwendige Quellcode notfalls auch von Hand angepaßt werden). Die Parser-Klassen AOTParse, AOTParseConstants, AOTParseTokenManager, ASCII_CharStream und ParseError werden vom Parser-Generator JavaCC 0.6ß (Sun) mit der Eingabedatei AOTParse.jj erzeugt. Diese enthält die Grammatik der Baum-Quelltexte (dazu später mehr). Wichtig: Nachdem JavaCC die Datei AOTParseTokenManager.java erzeugt hat, muß dort der Qualifizierer „private“ in der Deklaration des Objekts input_stream entfernt werden!

Der Quellcode der Klassen zum Properties-Dialog (PropsDialog und DialogLayout) werden vom J++ Java-Resource-Wizard aus der Dialog-Resource Props.rct erzeugt. Da die Properties-Sourcen recht einfach aufgebaut sind, ist eine nachträgliche Änderung wohl auch ohne Dialog-Resource möglich.

Insgesamt müssen folgende Quelldateien vorhanden sein:

AOT.java:

- Öffentliche Applet-Klasse, diverse unspezifische Methoden und Hilfsklassen (insbesondere elementare Datei-I/O und einfache Dialoge)

AOTParse.java:

- Parser für Baum-Quelltexte

AOTParseConstants.java:

- Konstanten für die Tokens des Parsers

AOTParseTokenManager.java:

- Lexikalische Analyse für den Parser

ASCII_CharStream.java:

- Eingabestream für den Parser

DialogLayout.java:

- Layout-Manager für den Properties-Dialog

Document.java:

- Faßt Baummodell und -ansicht zu einer Dokumentenklasse zusammen

Node.java:

- Baummodell als Datenstruktur und dessen Quelltext-Repräsentation

ParseError.java:

- Fehlerbehandlung des Parsers

PropsDialog.java:

- Dialogbox-Klasse zum Properties-Dialog

Token.java:

- Token-Klasse des Parsers

View.java:

- Klasse zur Baumansicht und darauf bezogene Hilfsklassen

Props.rct:

- Resourcenquelle für den GUI-Builder von Microsoft J++ (muß nicht unbedingt vorhanden sein, PropsDialog.java kann notfalls auch manuell verändert werden).

jb.java:

- HTML-Browser Frame zur Anzeige der Dokumentation. Einbindung in AOT.online_doc(). (©96 Alexey Goloshubin, Jeremy Cook (GNU), Erweiterungen: ©97 Matthias Nickles (GNU)).

BrowserInterface.java:

- Öffentliche Klasse zum HTML-Browser. Der nicht von Matthias Nickles angepaßte Teil des Browsers ist dokumentiert in Browser-Readme. Gegenüber dieser Variante hat der Browser viele Änderungen und Erweiterung erfahren, die nur im Quellcode dokumentiert sind.

FontInfo.java:

- Hilfsklasse zum HTML-Browser

Tag.java:

- Hilfsklasse zum HTML-Browser

Parser.java:

- Hilfsklasse zum HTML-Browser

./HTTPClient:

- Package zur gegenüber Javas .net-Package erweiterten Kommunikation per HTTP. Copyright © 1996,1997 Ronald Tschalaer (GNU)

Anmerkung zum HTML-Browser: Es handelt sich bei den genannten Dateien nicht um die Originale, sondern um modifizierte Versionen (entsprechende Stellen im Code sind mit *** gekennzeichnet). Der Original-Quellcode sind in browser.zip enthalten (GNU-Copyleft, siehe Datei license).

Unter J++ sind die oben genannten Dateien zu einem Projekt zusammenzufassen (Namensvorschlag: AOT.mdp) und mit den <Rebuild all>-Kommando zu übersetzten (nicht <Build AOT> verwenden, da dabei die Datei-Abhängigkeiten oft falsch bestimmt werden). Als executing class muß AOT an gegeben werden.

Die Übersetzung mit dem JDK erfolgt mit javac AOT.java. Es ist darauf zu achten, daß während (!) der Übersetzung der Java-CLASSPATH das Verzeichnis mit den neu zu erzeugenden Klassen enthält. Der Optimierungsschalter -O hat keine erkennbare Wirkung. Die Übersetzung mit dem JDK 1.0.2 dauert übrigens ca. 50 mal so lang wie mit J++ 1.0 !


Übersicht über die Programmstruktur

Folgendes Diagramm zeigt die grobe Programmstruktur. Dabei bedeutet "A --> B" dasselbe wie "B extends A", "X:x" bedeutet, daß x eine Instanz von X ist, und ein Attribut der in der vorangehenden Spalte stehenden Klasse.


Applet --> AOT : Aot (Appletebene)

........ Frame --> ApplicationFrame : application (optionale Anwendungsebene)

................ Document : document (Dokumentenebene; falls application fehlt, direkt

................ Member von AOT)

........................ Node : root (“Modellebene“, d.h. ein Und/Oder-Baum als Java-

........................ Datenstruktur)

........................ Panel --> View : view (“Ansicht-Ebene“)

................................. Canvas --> ViewCanvas : canvas (physikalische Ansicht)


Auffällig ist hierbei, daß die Anwendungsebene der Appletebene untergeordnet ist: das Programm läuft immer als Applet (auch wenn es nicht von einem Browser gestartet wurde), kann sich aber ggf. als Anwendung manifestieren (d.h. in einem resizable-Frame mit Pulldown-Menü, Icon usw.). Die verschiedenen Möglichkeiten des Aufrufs sind unter Invocation beschrieben. Die Applet-Klasse AOT ist die einzige öffentliche Klasse (abgesehen von Klassen, die von Java oder einem der oben genannten Quellcode-Generatoren "public" bereitgestellt werden). Sie besitzt genau eine Inkarnation in Gestalt des Objekts Aot. Das first-order-Fenster application kann man sich als Bedienungstool des Applets vorstellen. Hier findet der Anwender ein Menü und Shortcuts, mit denen er das gerade gelade ne Dokument (document) bearbeiten kann. Ein Dokument faßt die beiden Aspekte "Modell" und "Ansicht" eines Und/Oder-Baums zusammen: Das Baum-Modell instantiiert sich in der Klasse Document als Wurzelknoten root der Klasse Node, über den top-down auf alle an deren Teile des Baums zugegriffen werden kann. Dem Baum-Modell entspricht eine "Ansicht" view, deren Klasse View alle Aspekte der Baum-Darstellung zusammenfaßt, d.h. hauptsächlich die physikalische Darstellung in einem Canvas (Objekt canvas). Obwohl prinzipiell beliebig viele Dokumente zur selben Zeit vorhanden sein könnten, beschränkt sich die vorliegende Programmversion auf zwei Dokumente: das Hauptdokument document und die Zwischenablage (clipboard). Da die Zwischenablage technisch gesehen nur eine einge schränkte Variante des Hauptdokuments ist, beziehen sich alle folgenden Erläuterungen auf jenes.


Die Klasse Node (“Modellebene“) -> class Node

Ein Objekt dieser Klasse beschreibt einen Knoten eines Und/Oder-Baums, ggf. einschließlich seiner Kind-Knoten sowie seine zulaufende Kante. Um unnötige Fallunterscheidungen zu vermeiden, gilt letzteres auch für den Wurzelknoten, dem dazu eine Dummy-Kante zugewiesen wird. Der Begriff „Knoten“ bezeichne je nach Kontext sowohl den gesamten Unterbaum als auch nur das Speicher-Objekt der Klasse Node selbst. Das Wurzelobjekt bzw. seine innere Struktur wird auch als "Baum-Modell" bezeichnet, im Unterschied zur "Baum-Ansicht", die von der Klasse View realisiert wird. Es gibt vier Unterklassen der abstrakten Klasse Node: AND_Node, OR_Node, iOR_Node und Text_Node (Text-Blätter). Dabei umfaßt die Klasse Node selbst diejenigen Knoten-Attribute und -Methoden, die für mindestens zwei der abgeleiteten Klassen sinnvoll sein können. Dazu zählen der Knotenname, die zulaufende Kante (Klasse Edge) sowie die Attribute "Hilfetext" und " Expansionsstatus" (d.h. ob der Knoten und seine Abkömmlinge in der Baumansicht expandiert oder versteckt dargestellt werden sollen). Die wichtigsten Membervariablen werden im folgenden noch genauer beschrieben. Außerdem zeigt jeder Knoten auf seinen Vater (parent, null für den Wurzelknoten) und ggf. seine Kinder (children[], null bei einem Text-Blatt, [0] bei sonstigen Blättern!). Die Abkömmlinge müssen bekannt sein, um den Baum (meist von der Wurzel ausgehend) top-down durchlaufen zu können. Der Knoten-Vater muß bekannt sein, weil die Aktivierung eines Knotens die Aktivierung des Vaterknotens bedingt. Der super-Konstruktor Node() weist genau jene Attribute zu. Methoden, die nur auf diese Attribute Bezug nehmen, sind in der Klasse Node deklariert. Ist der Methodenrumpf dabei für alle Unterklassen von Node verschieden, oder läßt der Methodenrumpf eine hinreichende "Allgemeingültigkeit" vermissen, so wird die Methode als "abstract" deklariert und erst in den Unterklassen ausformuliert. Es ist daher nicht möglich, die Klasse Node direkt zu instantiieren.

Die wichtigsten allgemeinen Statusvariablen (Attribute) eines Knotens:

- zulaufende Kante (“leading edge“):

Das Objekt edge der Klasse Edge beschreibt die Kante, die den Knoten mit seinem parent-Knoten verbindet. Dabei sind die verschiedenen Members von Edge nur der Übersichtlichkeit wegen in einer eigenen Klasse angeordnet; sie bilden mit dem eigentlichen Knoten eine Einheit, was auch bei der Bedienung des Programms zum Tragen kommt (vgl. editable object). Methoden, die sich nur auf die Kante selbst beziehen, sind in Edge deklariert. Methoden, die sich zwar auf Kanten beziehen, aber diese erst über die Baumstruktur bestimmten müssen, befinden sich dagegen in der Klasse Node (activate_edges(), find_node_by_edge(), update_edge_id_no()).

Erwähnenswert ist der Mechanismus zur Zuweisung eines Identifikators für eine Kante. Bei allen Operationen, die eine Kopie einer Kante erzeugen, wird sichergestellt, daß die neue Kante einen eindeutigen Identifikator erhält. Dazu ruft der Copy-Konstruktor copy() eine Routine auto_generate_id() auf, die aus dem Label der neuen Kante einen Identifikator erstellt. Dazu ist in Edge als Inhalt einer statische Variable eine Zahl vorhanden, die als Bestandteil eines automatisch erzeugten Identifikators noch nie verwendet worden ist. Dies wird dadurch sichergestellt, daß beim Laden eines neuen Dokumentes der höchste darin vorkommende Index aller automatisch erzeugten Kanten-Identifikator festgestellt wird. Es würde zwar ausreichen, wenn der Identifikator nur unter den Geschwistern der Kante eindeutig ist, der Mehraufwand zur Erzeugung global eindeutiger Bezeichner ist aber minimal (wenn überhaupt vorhanden) und kann vernachlässigt werden.

Der programmtechnische Ablauf der Aktivierung von Kanten (bzw. Ihrer nachfolgenden Knoten, was dasselbe ist) durch Selektionen wird bei der Beschreibung der Node-Methoden erläutert.


- Knoten-Name (Include-Dateiname):

Jedem Knoten kann optional ein Bezeichner zugeordnet werden. In der aktuellen Programmversion muß dies ein Dateiname sein, es wäre aber auch denkbar, dem Programm eine eigene, vom Dateisystem unabhängige Speicherverwaltung hinzuzufügen. Trifft der Parser im Baum-Quelltext auf einen Include-"Knoten" mit Namen n, so fügt er am Ort des Include-Knotens den geparsten Dateiinhalt der Datei n in den Baum als Unterbaum ein. Ist n mit einem relativen Pfad gegeben, so bezieht sich der Pfad auf das Verzeichnis der einschließenden Datei (also der Datei, die den Include-Knoten enthält). Der Wurzelknoten des Unterbaums erhält den Knoten-Namen n. Nach Öffnen eines Baumdokuments kann der Superuser Knoten-Namen hinzufügen oder modifizieren. Beim Umwandeln des Baums in einen String werden die Knoten-Namen optional berücksichtigt.

Die wichtigsten Operationen auf Knoten, die (virtuell) von der Oberklasse Node bereitgestellt werden:


- Darstellung:

Obwohl die tatsächliche Darstellung eines Baums über die Klasse View erfolgt, muß diese Darstellung in der Klasse Node "angestoßen" werden, um dem objektorientierten Paradigma gerecht zu werden und die beiden Ebenen "Baum-Modell" und "Baum-Ansicht" weitest gehend zu trennen: Durch Aufruf der Methode display() stellt sich ein Knoten selbst dar. Diese Methode ruft dazu Hilfsroutinen in der Klasse View auf. View kennt dabei weder den Wurzelknoten noch die Baumstruktur; z.B. sind das Zeichnen des Knoten-Symbols und das Zeichnen der Abkömmlinge eines Und- oder Oder-Knotens zwei verschiedene Vorgänge innerhalb von class View, die beide von display() aufgerufen werden.


- Kopie:

Node ist "cloneable", d.h. es ist mittels der in Java vordeklarierten Methode clone() in Node möglich, einen Knoten so zu duplizieren, daß das Duplikat hinterher unabhängig vom Original manipuliert werden kann (d.h. ohne Mehrfachreferenzierung von Memberob jekten). Basis dieser Operation sind die für jede der drei Unterklassen spezifischen Methoden create(). Diese Methoden stellen "Objekt-Fabriken" dar, die nicht nur eine exakte Kopie eines Objektes der jeweiligen Unterklasse erzeugen können, sondern auch eine (Teil-)Kopie eines Objekts einer beliebigen anderen Unterklasse. So ist es z.B. möglich, mit Hilfe der create()-Methode der Unterklasse Text_Node einen neuen Text-Knoten aus einem Oder-Knoten zu erzeugen. Diese Fähigkeit wird vom "Properties"-Dialog zum "Cast" von Knoten typen benötigt. Da diese Methoden ohne Bezug auf "this" auskommen, sind sie statisch deklariert.


- Umwandlung in einen String:

Hierzu stehen zwei Methoden mit Namen toString bereit: toString(Stack includes, String indent, boolean top) und toString(). Letztere ist im lang-Package von Java vordeklariert und ruft die Variante der erstgenannten Methode auf, die keine Knoten-Namen berücksichtigt (d.h. keine Include-"Knoten" im Quelltext erzeugt). Der Parameter includes der erweiterten Methode referenziert einen Stack mit Objekten vom Typ Node, zu dem jedesmal ein neues Element hinzugefügt wird, wenn toString auf einen Knoten-Namen trifft. In diesem Fall resultiert toString in einem Include-"Knoten". Andernfalls wird die Textrepräsentation des jeweiligen Knotens erzeugt und toString ggf. rekursiv für die Kinder des Knotens aufgerufen. In beiden Fällen wird dem Resultat natürlich noch die Textrepräsentation der vorlaufenden Kante (edge.toString()) vorangestellt. Nach jedem Aufruf von toString durch die Routine zum Speichern eines Baums als Datei (Document.save()) wird dort überprüft, ob der Stack includes Elemente enthält. Wenn ja, so wird für das oberste Stackelement (ein Knoten mit dem Knoten-Namen n) toString(includes, ...) aufgerufen und das Resultat in der Include-Datei n gespeichert. Der Knoten wird vom Stack entfernt und der gesamte Vorgang wiederholt sich, bis keine Elemente mehr auf dem Stack liegen.

Die Syntax der Textdarstellung ist unter Textuelle Repräsentation und Parser AOTParse beschrieben.


- Aktivierung und Deaktivierung (Hinzufügen und Entfernen eines Knotens zur/aus der aktuellen Selektion):

Es gibt zwei Mechanismen zur Veränderung des Aktivierungsstatus eines Knotens (der Begriff "Knoten" soll hier als Objekt der Klasse Node auch die zulaufende Kante einbeziehen).

Zum einen kann die Aktivierung einzelner Knoten gezielt mit der Methode toggle_activation() umgeschaltet werden (ausgenommen ist der Wurzelknoten, der definitionsgemäß immer aktiv ist). Diese Methode führt ggf. weitere (De-)aktivierungen durch, wenn diese zwingend erforderlich sind: Wird ein Knoten k aktiviert, so wird auch der Vaterknoten von k aktiviert. Handelt es sich beim Vater-Knoten um einen Oder-Knoten, so wird das Kind als aktuelle Auswahl in den Status des Oder-Knotens übernommen. Das vorher aktive Kind wird deaktiviert. Beim Deaktivieren eines Knotens k wird zuerst geprüft, ob der Vater-Knoten ein Und-Knoten ist. Wenn ja, so reicht es bereits aus, dessen Aktivierungszustand umzuschalten (dieser Vorgang setzt sich ggf. bis vor die Wurzel fort). Wenn nicht, so muß der Vater ein Oder-Knoten sein. In diesem Fall wird in der Methode deactivate() die Auswahl im Status des Vaterknotens gelöscht. Außerdem werden dort ggf. die Kinder von k deaktiviert. Im Zuge dieser Operationen könnte man auch dazu parallel die graphische Darstellung der betroffenen Baumteile anpassen. Dies geschieht deshalb nicht, weil in einem objekt- und ereignisorientierten System wie Javas AWT die graphische Darstellung prinzipiell ein zur Konstruktion bzw. Veränderung des Modells ("Baum") asynchroner Vorgang ist (dies ist nicht zwingend so, entspricht aber am besten dem AWT-Paradigma): Ein vom GUI ausgehendes Event ruft zu einem (von der Modellebene aus gesehen) undefinierten Zeitpunkt die Prozedur paint() auf. Daraufhin stellt sich der Baum selbst dar: Der Status des Baums wird gelesen und ein entsprechendes Diagramm gezeichnet (näheres siehe bei Klasse View). Auf diese Weise sind Modell-Konstruktion (“Status-Veränderung“) und Modell-Ansicht (“Status-Examinierung“) operationell fast vollständig voneinander getrennt. Eine Abkehr von diesem Prinzip wäre allerdings notwendig, wenn die graphische Darstellung erheblich aufwendiger ausfallen würde (z.B. wenn sehr große Bäumen der Regelfall wären).

Die zweite Möglichkeit, Knoten zu aktivieren, besteht in der Ausführung von Selektionen.

Eine Selektion ist eine Zeichenkette mit folgender Syntax:

Selection ::= "(" <Edges> ")"

| ;

Edges ::= <Edges> "," <Edge>

| <Edge> ;

Edge ::= <Edge_Identifier><Selection>

| "\""<Leaf_Text>"\""

| "_" ;

Beispiel: (kante1("Blatt1"), kante2(kante21()), kante3(kante31, kante32("Blatt32")))

Leaf text kann auch im HTML-Format vorliegen. Die Methode activate_edges(String selection) parst eine Selektion und aktiviert dabei rekursiv die Knoten, deren Kanten-Identifikatoren in der Selektion vorkommen. Dazu wird die Selektion mittels der Java-.io-Klasse StreamTokenizer in einen Strom aus Bezeichnern (die Kanten-Identifikator), Strings (die Blatt-Texte) und Trennzeichen (Klammern und Kommas zerlegt (Vorsicht: Es gibt im util-Package eine weitere Klasse namens StreamTokenizer, die bei weitem nicht so leistungsfähig ist). Mit diesem Stream (und implizit der aktuellen Lese-Position im Stream) als Argument wird die Methode activate_edges(StreamTokenizer selstream) des Wurzelknotens aufgerufen. Ist das aktuelle Token im Stream ein Identifikator oder eine Liste von Identifikatoren, so werden die Kinder des Knotens mit entsprechenden Kanten-Identifikatoren aktiviert (die Reihenfolge der Identifikator bei Und-Knoten spielt dabei keine Rolle). Folgt nach einem Identifikator-Token in Klammern eine weitere Selektion, so wird activate_edges() rekursiv für den dem Identifikator entsprechenden Kind-Knoten aufgerufen. Folgt in Klammern der Unterstrich ("_"), so deutet dies darauf hin, daß im Baum noch weitere Kanten vor dem Blatt vorkommen, diese aber nicht aktiviert sind ("Unvollständige Selektion" ). Ist das aktuelle Token ein String und der Knoten vom Typ Text_Node, so wird der Knotentext ausgetauscht. Zu beachten ist, daß activate_edges() Knoten nur dann deaktiviert, wenn deren Aktivierung nicht mit der Selektion selection vereinbar ist (d.h. wenn ein Kind eines Oder-Knotens aktiv ist und selection ein anderes Kind aktivieren will, vgl. Methode activate()). Daher ist vor dem Aufruf von activate_edges() ggf. der minimale Aktivierungszustand (wieder-)herzustellen (d.h. fuer alle Oder-Knoten gelte active_child = null). Dies erledigt die Methode reset_activation(). Die zum aktuellen Aktivierungszustand passende Selektion erhält man mit der Methode get_selection().


Constraints

Unter einem Constraint ist in Categories ein Ausdruck der Form

Selektion_h => Selektion_t1, Selektion_t2,..., Selektion_tn

zu verstehen. Wird ein solcher Constraint definiert (Befehl <Constraints...> im <Selection>-Menü), so wird bei jeder Änderung des Aktivierungszustandes geprüft, ob die Selektion_h (die ebenso wie Selektion_t..n auch unvollständig sein darf) bei hypothetischer Aktivierung nur Kanten aktivieren würden, die in der aktuellen Selektion bereits aktiviert sind (man sagt, die Selektion_h „matcht“). Ist dies der Fall, so wird nachfolgend geprüft, ob dies auch mit mindestens einer der Selektionen tn gilt. Ist letzteres nicht der Fall, so wird eine Warnmeldung angezeigt (der Constraint wurde „verletzt“). Der Inhalt von Textknoten wird bei der Prüfung übrigens ignoriert. Eine mögliche zukünftige Erweiterung wäre, hier ein reguläres String-Matching einzuführen.

Constraints werden in der .cat-Datei abgelegt (siehe Grammatik). Programmiertechnisch werden sie einfach durch eine Abänderung des Algorithmus zur Aktivierung von Kanten geprüft: Statt für in den Selektionsmustern vorhandene Identifikator die entsprechende Kante zu aktivieren, wird geprüft, ob die Kante schon aktiviert ist.


Besonderheiten der Unterklassen von Node:


Die Unterklasse AND_Node (ramification):

Dieser Knotentyp weist keinerlei Besonderheiten auf; er fügt der Superklasse Node keine weiteren Member-Variablen hinzu. Da die Aktivierung eines Und-Knotens die Aktivierung seiner Kinder und seines Vaterknotens impliziert, ist es nicht notwendig, den Aktivierungsstatus im Und-Knoten selbst zu speichern.


Die Unterklasse OR_Node (decision node):

Gegenüber Node verfügt ein Oder-Knoten zusätzlich über die Variable active_child, die auf das aktivierte Kind-Knoten zeigt bzw. null enthält. Zur Berücksichtigung dieser Variablen sind in der Klasse OR_Node diverse Methoden zur Verwaltung von Knoten-Kinder n überschrieben (z.B. remove_child()). Der genaue Ablauf der Aktivierung eines Knotens wird unter Aktivierung und Deaktivierung geschildert.


Die Unterklasse iOR_Node (multi-decision node):

Wie OR_Node, es können aber mehrere Kinder gleichzeitig aktiviert sein. Die Implementation unterscheidet sich im Prinzip nur darin, daß statt active_child ein entsprechendes Feld vorhanden ist.


Die Unterklasse Text_Node (text leaf):

Textknoten nutzen den in der Klasse Node vorgesehenen Eintrag children[] nicht; er wird auf null gesetzt. Für die zusätzlichen Einträge text, rows und columns ist eine eigene Infrastruktur vorhanden: Neben intern verwendeten einfachen Methoden zu Schreiben und Lesen dieser Einträge gibt es die Methode edit() zur Erzeugung eines Dialog-Objekts der Klasse TextLeafDialog.


Textuelle Repräsentation und Parser AOTParse -> AOTParse.jj

Jeder Knoten und mithin der gesamte Baum besitzen eine Darstellung als Text, die insbesondere zur Speicherung eines Dokuments in einer „Quellcode“-Datei verwendet wird. Die textuelle Darstellungsform eines Knotens setzt sich aus einem Präfix zusammen, daß den Typ des Knotens angibt (and, or, text, include) sowie, in Klammern dahinter, einer näheren Spezifikation des Knotens, die dem Statusbereich (Menge der Membervariablen) der entsprechenden Unterklassen der Klasse Node entspricht. Diese Spezifikation kann man auch als Liste von Argumenten vom Präfix ansehen, wobei das Präfix bei dieser Sichtweise einer Vorschrift zur Konstruktion eines Knotens gleichkommt (auszuführen beim Parsen, s.u.). Die Präfix-Schreibweise wurde gewählt, um die Syntax kompatibel zur Programmiersprache Prolog zu machen. Die Argumente gliedern sich in "Hauptargumente" (die Kinder bei Und/Oder-Knoten, den Text bei Text-Knoten, den Dateinamen bei Include-"Knoten") und optionale "Attribute" (den Hilfetext, die Ausmaße des Eingabefensters zum Inhalt von Textblättern, ob der Unterbaum kollabiert dargestellt werden soll). Die Attribute sind benannt (z.B. help="Hilfetext"), ihre Reihenfolge daher beliebig. Fehlt in einem Baum-Quelltext (d.h. beim Öffnen eines Dokuments) ein Attribut, so wird der Knoten mit ein e m Default-Wert erzeugt (die Default-Werte sind als Konstanten in der jeweiligen Unterklasse von Node vorgegeben). Hauptargument und Attribute beschreiben zusammen den gesamten Knotenstatus (siehe class Node), ausgenommen seinen Aktivierungszustand und den optionalen Knotennamen. Der Knotennamen wird durch den Include-“Knoten“ der einschließenden Datei vergeben, der Aktivierungszustand kann erst nach dem Parsen des Baums verändert werden.

Und- und Oder-Knoten verlangen als Hauptargument eine [Liste] mit Kindern. Diese darf leer sein (was allerdings nur bei Und-Knoten Sinn macht; es handelt sich dann um sogenannte endings). Jedes Kind wird wieder in der beschriebenen Syntax aufgeführt, wobei davor, getrennt durch einen Doppelpunkt, die Kante zu beschreiben ist, die das Kind mit dem Vaterknoten verbinden soll. Auch hier wird die Präfix-Schreibweise verwendet. Präfix ist der Identifikator der Kante (eine Name aus Buchstaben, dem Unterstrich und Zahlen, beginnend mit einem Kleinbuchstaben. Unter Prolog entspricht dies einer Konstante). Danach folgen optional in Klammern die (benannten) Attribute der Kante. Diese sind ein Hilfetext und die Beschriftung (“label“) der Kante. Die genaue Syntax ist weiter unten angegeben. Eine Besonderheit stellen die Include-"Knoten" dar. Sie haben keine Entsprechung zu einer eigenen Unterklasse von Node, sondern verweisen lediglich auf einen weiteren Knoten, dessen Textdarstellung in einer anderen (Include-) Datei gespeichert ist (näheres siehe unter Knoten-Name).

Die Umwandlung eines Objekts der Klasse Node in seine Textdarstellung erfolgt mit der Node-Methode toString(). Die umgekehrte Richtung beim Öffnen eines Baum-Dokuments erledigt der Parser AOTParse. Dieser Parser wird vom Tool JavaCC automatisch aus der Grammatik AOTParse.jj generiert. Der Quelltext eines Baums wird als Objekt der Klasse AOTreeString verwaltet. Diese Klasse besteht im wesentlichen nur aus dem eigentlichen String und Methoden zum Parsen und Anzeigen dieses Strings. Der Parser arbeitet nach der Methode des "rekursiven Abstiegs", d.h. es ist für jedes Nichtterminal im Eingabetext (hier der Quelltext des Baums) eine Prozedur vorhanden, die rekursiv (top-down) weitere Nichtterminale parst. Die zum Nichtterminal <Node> (siehe Grammatik unten) gehörige Prozedur node (Node parent, Edge edge) erzeugt dabei die Knoten und bindet sie an ihre Vaterknoten (beim Aufruf des Parser-Konstruktors wird für edge ein Dummy-Objekt angegeben). Prinzipbedingt werden die Blätter zuerst vollständig erzeugt (da für sie keine Kind-Knoten rekursiv geparst werden müssen), während dagegen der Wurzelknoten zuallererst vom Parser „gelesen“ wird. Weitere Prozeduren parsen die Argumenteliste eines Knotens und seine zulaufende Kante. Wichtig ist, daß beim Aufruf des Parsergenerators JavaCC die Option STATIC=false angegeben wird. Das ist notwendig, da beim Parsen von Include-Knoten weitere Instanzen des Parsers erzeugt werden müssen. Es wäre zwar alternativ vielleicht möglich gewesen, den Status des aktuellen Parsers zu sichern und dann für die Include-Datei zurückzusetzen, doch wären der damit verbundene Aufwand und die hohe Fehleranfälligkeit dieses Verfahrens gegenüber der potentiellen Speicherplatzersparnis nicht zu rechtfertigen. Außerdem muß der Modifizierer private bei der Deklaration des Streams input_stream in AOTParseTokenManager.java nachträglich entfernt werden (Categories greift auf diesen Stream direkt zu).


Übersichtsgrammatik (in YACC-ähnlicher Syntax):


<Document> ::= <Node>

| categories(<Node>). [ <Constraints> ] [ <Bookmarks> ]

<Node> ::= and([<Children>])

| and([<Children>], <And_Attributes>)

| or([<Children>])

| or([<Children>], <Or_Attributes>)

| ior([<Children>])

| ior([<Children>], <iOr_Attributes>)

| text()

| text(<String>)

| text(<Text_Attributes>)

| text(<String>, <Text_Attributes>)

| include("URL/Dateinamen") ;


<Common_Attribute> ::= <Help_Attribute>

| <Collapse_Attribute> ;


<Help_Attribute> ::= help=<String> ;


<Collapse_Attribute> ::= collapsed=<Boolean> ;


<Boolean> ::= yes | no | true | false ;

<And_Attributes> ::= <And_Attributes> , <And_Attributes>

| <Common_Attribute> ;


<Or_Attributes> ::= <Or_Attributes> , <Or_Attributes>

| <Common_Attribute> ;


<iOr_Attributes> ::= <iOr_Attributes> , <iOr_Attributes>

| <Common_Attribute> ;


<Text_Attributes> ::= <Text_Attributes> , <Text_Attributes>

| <Common_Attribute>

| <Rows_Attribute>

| <Cols_Attribute>;


<Rows_Attribute> ::= rows=<Integer> ;


<Cols_Attribute> ::= cols=<Integer> ;


<Children> ::= <Childs>

| ;


<Childs> ::= <Child>

| <Childs> , <Child> ;


<Child> ::= <Edge> : <Node> ;


<Edge> ::= <Identifier>

| <Identifier>(<Edge_Attributes>) ;


<Edge_Attributes> ::= <Edge_Attributes> , <Edge_Attributes>

| <String>

| label=<String>

| <Help_Attribute> ;


<Constraints> ::= <Constraint>

| <Constraints> , <Constraint> ;


<Constraint> ::= constraint( <Head-Selection>, \[ <Selections> \], <Constraint_Attributes> ). ;


<Head-Selection> ::= <Selection>;


<Selections> ::= <Selection>

| <Selections>, <Selection>;


<Constraint_Attributes> ::= <Constraint_Attributes> , <Constraint_Attributes>

| label=<String>

| <Help_Attribute>

| suspended=<Boolean>;


<Bookmarks> ::= <Bookmark>

| <Bookmarks> , <Bookmark> ;


<Bookmark> ::= selection( <Selection>, <Bookmark_Attributes> ). ;


<Bookmark_Attributes> ::= <Bookmark_Attributes> , <Bookmark_Attributes>

| label=<String> ;


(Die Syntax von <Selection> siehe Selektions-Grammatik)


Die Klasse View (“Ansicht-Ebene“) -> class View

Ein Objekt der Klasse View dient der graphischen Darstellung eines Und/Oder-Baums. Jedes Dokument besitzt genau ein solches Objekt (genannt Baum-"View" oder -"Ansicht"), welches mit dem Baum-Modell (siehe Klasse Node) korrespondiert. Die Klasse View umfaßt dazu graphische Komponenten (Ausgabebereiche und elementare Bedienelemente wie z.B. Scrollbars) und die zugehörigen Methoden zum Zeichnen und Betrachten des Baumdiagramms. Funktionalität, die sich auf die Baumstruktur selbst bezieht (d.h. über die Darstellung einzelner Kn oten hinausgeht), ist praktisch nicht vorhanden; alle Vorgänge, die den Baum als ganzes betreffen und nicht nur einzelne äußerlichen Aspekte (wie z.B. die Methode draw()) rufen Methoden der Klasse Node auf (siehe class Node - Darstellung). Da die Baumansicht nichts über die innere Struktur des Baummodells "weiß", kann dasselbe View-Objekt verschiedene Bäume anzeigen. Die einzige dauerhafte Schnittstelle vom View zum Baummodell ist das gemeinsame Dokument.

Es handelt sich bei der Klasse View technisch um eine Erweiterung der AWT-Klasse Panel (ein Container ohne eigenen Rahmen, der als Komponente in jeden anderen Container integriert werden kann). Dadurch daß das View über keinen eigenen Rahmen verfügt, kann es sehr flexibel verwendet werden. Die Baum-Ansicht ist so unabhängig vom umfassenden ApplicationFrame bzw. Applet (bei einem Aufruf durch einen Browser) und nutzt ihre Kenntnis vom Dokument, in dem sie enthalten ist, nur zur Darstellung des Wurzelknotens (siehe class Node - Darstellung). Die eigentliche Darstellungsfläche (also der View-"Innenraum") bildet ein Objekt der Klasse ViewCanvas (Member canvas). ViewCanvas ist von der AWT-Klasse Canvas abgeleitet, um ein eigenes Event-Handling implementieren zu können. Außerdem enthält View als Komponenten die Scrollbars und einen Bereich zur Anzeige von Hilfetexten. Das Event-Handling beschränkt sich auf solche Ereignisse, die unmittelbar mit der Darstellung des Baumdiagramms verbunden sind (Scrolling mittels Cursor-Tasten und den Scrollbars, Größenänderung des Views infolge der Größenänderung des umfassenden Containers). Events zum Neuzeichnen des View-Innenraums (Canvas-Bereich) werden bereits von der Klasse ViewCanvas abgefangen. Dies gilt auch für Mausereignisse, die sich auf den Canvas-Be reich beziehen. Events mit "höheren Aufgaben" (Editieren des Baums usw.) werden dagegen erst im ApplicationFrame verarbeitet, was den Vorteil hat, daß so fast die gesamte Funktionalität eines Baums unabhängig von seiner konkreten Darstellung ist.


Zeichnen des Baumdiagramms:

Wichtigste Aufgabe von View ist das Zeichnen des Baumdiagramms. Die dafür zuständige Methode draw() wird entweder vom Dokument aus aufgerufen, wenn am Baum eine Änderung vorgenommen wurde, die ein Neuzeichnen erforderlich macht, oder vom GUI durch Erzeugung eines entsprechenden Events. Diese Events werden durch die Methode ViewCanvas.update() (Neuzeichnen mit vorherigem Löschen des Canvas-Bereichs, z.B. nach dem Entfernen oder Einfügen von Knoten) und ViewCanvas.paint() (Neuzeichnen ohne vorheriges Löschen des Can vas-Bereichs, z.B. bei einer Veränderung des Aktivierungszustandes des Baums) aufgefangen. Eine weitergehende Differenzierung beim Neuzeichnen ist nicht realisiert, da der Aufwand und die eventuelle Verkomplizierung gegenüber den zu erwartenden Baumgrößen nicht lohnen (vgl. Aktivierung und Deaktivierung). Um lästiges Flackern bei der physikalische Darstellung zu vermeiden, zeichnet update() das Diagramm zuerst in einen Pufferspeicher, dessen Inhalt danach quasi "schlagartig" dargestellt wird. Bei diesem Puffer handelt es sich um ein Objekt der AWT-Klasse Image (entspricht unter Windows in etwa einem Device-Independent-Bitmap). Beim Neuzeichnen ohne vorheriges Löschen (durch paint() oder den direkten Aufruf von draw()) ist diese sogenannte "Doppelpufferung" nicht notwendig. Um die Doppelpufferung auszuschalten (z.B. um Speicher zu sparen), kann in View die Konstante BUFFER auf false gesetzt werden. In allen Fällen wird aber ein Graphics-Objekt ermittelt, daß als Ziel der folgenden Zeichenoperationen dient (ein Graphics-Objekt ist vergleichbar mit einem Device-Context unter Windows. Leider funktioniert dieses Konzept unter Java 1.0 nicht mit Druckern). Dieses Graphics-Objekt ist den Methoden in View über die Membervariable target_g bekannt. Da sich das Ziel-Graphics somit jederzeit austauschen läßt, kann das Baumdiagramm an jeden Ort "umgeleitet" werden, dessen Graphics-Kontext ermittelt werden kann (siehe z.B. Methode print()). Der zum Zeichnen des Baums notwendige Top-Down-Durchlauf wird durch Aufruf der virtuellen Methode Node.display() des Wurzelknotens initiiert (siehe class Node - Darstellung). Von dort aus werden dann die Methoden zum Zeichnen der Knoten-Symbole (draw_ AND_Node(), draw_OR_Node()) bzw. des Knoten-Textes (draw_Text_Node()) aufgerufen. Die Darstellung der Kanten erledigt draw_children() (dies ist ein von draw_.._Node getrennter Vorgang, der wiederum von Node.display() ausgeht). Dort werden auch die display () -Methoden der Kind-Knoten aufgerufen. Die Kinder eines Knotens werden eingerückt unterhalb des Vaterknotens gezeichnet. Daher ist es erforderlich, daß display() die Höhe des gezeichneten Bereichs (also des gesamten Unterbaums) an die aufrufende Methode draw_children() zurückmeldet. Das dazu verwendete Objekt der Klasse Area enthält noch weitere Informationen (Rechteck um die anklickbare Fläche des Knotens). Diese Informationen werden im Zusammenhang mit der im folgenden beschriebenen Markierung von Kanten und Knoten benötigt. Da Baumdiagramme größer als der Canvas-Bereich werden können, wird von allen Ausgabepositionen ein Offset (offset_x, offset_y) abgezogen, dessen Höhe durch die Scrollposition bestimmt wird. Dadurch werden die Koordinaten zu Positionen außerhalb des sichtbaren Bereichs negativ bzw. größer als die vertikale bzw. horizontale Größe des Canvas-Bereichs. Das automatische Clipping bei der physikalische Ausgabe zum Graphics-Objekt bewirkt dann, daß Graphikausgaben an diesen Positionen nicht dargestellt werden.


Markierung des editable object:

Um einen Knoten und dessen zulaufende Kante schnell und einfach graphisch markieren zu können, ist es erforderlich, die "neuralgischen Bereiche" (d.h. die Bereiche, die der Anwender im Baumdiagramm durch Mausklick o.ä. selektieren kann, also Knotensymbole, Kanten und der Text von Textblättern) aller Kanten und Knoten des Baums in einer Tabelle zu speichern. Diese Tabelle wird im Zuge des Zeichnens des Baums in der Methode draw_children() aufgefüllt, falls das Flag collecting_edges gesetzt ist. Dies ist dann der Fall, wenn die Tabelle noch nicht existierte bzw. wenn die alte Tabelle ungültig geworden ist und daher gelöscht wurde (z.B. nach einer destruktiven Veränderung des Baums). In beiden Fällen wird von draw() das genannte Flag gesetzt, was in draw_children() dazu führt, daß die Rechtecke, welche die genannten neuralgischen Flächen umgeben, zusammen mit dem dazugehörigen Knoten als Objekt der Klasse Neuralgic in die Tabelle edge_table eingetragen werden. Der Eintrag erfolgt dabei in der tabellarischen Reihenfolge der Kanten und Knoten im Baumdiagramm, während die zeitliche Reihenfolge der Tabelleneinträge durch den Depth-first-Charakter dieses Vorgangs bestimmt wird: um eine neuralgische Fläche in edge_table eintragen zu können, muß bei der vorliegenden Implementation zuerst der entsprechende Knoten einschließlich seiner Kinder gezeichnet worden sein. Aus diesem Grund ist edge_table als Java-util.Directory-Objekt ausgeführt, dessen Schlüssel die Zeilennummern der neuralgischen Fläche im Baumdiagramm sind. Die Zeilennummer ergibt sich in der Funktion find_neuralgic_row() aus einer vertikalen Koordinate durch einfache Division, da alle Kanten und ihre nachfolgenden Knoten im Baumdiagramm die gleiche Höhe haben. Mit etwas Mühe könnte man auch mit einer einfachen Liste auskommen, was aber keinen praktischen Nutzen mit sich bringen würde. Um nach Erstellung der Tabelle zu einer Koordinate (geliefert z.B. durch ein Mausereignis) das entsprechende Objekt der Klasse Neuralgic zu finden (und damit den angeklickten Knoten), wird die Methode get_neuro() aufgerufen. Dort wird aus der Koordinate wie oben gezeigt der Schlüssel berechnet und das zugehörige Tabellen-Element abgerufen.


Die Klasse Document -> class Document

Ein Objekt dieser Klasse ("Dokument") faßt Baum-Modell (als Wurzelobjekt root der Klasse Node) und Baum-Ansicht (als Objekt view der Klasse View) zu einer Einheit zusammen. Außerdem ist ein Member source der Klasse AOTreeString vorhanden, welches den zuletzt geparsten Baumquelltext enthält (s.u.). Alle Funktionalität, die sich auf root und view gemeinsam auswirkt, wird über das Dokument gesteuert. Zu beachten ist, daß ein Document-Objekt kein eigenes graphisches Objekt ist. Der Aufruf der verschiedenen Methoden in der Klasse Document durch den Anwender erfolgt vielmehr z.B. mittels eines Fensters der Klasse ApplicationFrame. Daher ist es denkbar, zu einem Dokument mehrere Befehlsschnittstellen zur Auswahl zu stellen (ansatzweise ist dies in der vorliegenden Programmversion durch die Möglichkeit, das Dokument in die HTML-Seite einzubetten statt in einem eigenen Fenster anzuzeigen, realisiert). Insgesamt verfügt die Klasse Document kaum über eigene Funktionalität, sondern bietet vielmehr einheitliche Schnittstellen zu den Methoden in Node und View. Außerdem wacht das Dokument über die Speicherungs-Bedürftigkeit des Baums: Im Superuser-Modus wird bei jedem Editiervorgang das Flag modified gesetzt, im Endanwender-Modus bei einer Veränderung des Aktivierungszustandes des Baums oder des Inhaltes eines Text-Blattes. Verläßt der Anwender das Programm oder will er einen anderen Baum laden, so wird er vorher bei gesetztem modified-Flag aufgefordert, den bisherigen Baum bzw. die Selektion als Datei zu speichern. In der vorliegenden Programmversion gibt es genau zwei Dokumente: das Hauptdokument (Aot.document) und das Clipboard (Aot.clipboard). Letzteres wird nur optional angezeigt.

Laden, Speichern und Editieren von Baum-Quelltext:

Beim Laden eines Baums durch die Methode open(String filename), die auch schon der Konstruktor aufruft, wird die Textrepräsentation aus der .cat-Datei gelesen (die Low-Level-Funktionen zum Dateihandling befinden sich in der Klasse AOT) und daraus in parse( ) ein Node-Objekt erzeugt. Diese Vorgang ist unter Textuelle Repräsentation und Parser AOTParse beschrieben. Schlägt eine dieser Operationen fehl, so wird der vorhergehende Baum wiederhergestellt. Ist dies nicht möglich (z.B. direkt nach dem Programmstart), so wird ein Baum erstellt, der nur aus einem Und-Knoten ohne Kinder besteht. Besitzt das Dokument zu diesem Zeitpunkt noch kein view, so wird eines erzeugt. Andernfalls wird das bisherige weiterverwendet (es gibt keine fixe Zuordnung Baummodell<->Baumansicht; die Baumansicht zeigt jeden beliebigen Baum an, der im Dokument enthalten ist. Mehr dazu siehe Klasse View). Auch nach dem Parsen bleibt der Baum-Quelltext in der Variablen source erhalten. Die Klasse von source (AOTreeString) besitzt die Fähigkeit, den Baum-Quelltext in einem nicht-modalen Editorfenster ("Categories text view") parallel zur Baumansicht anzuzeigen. Initiiert wird dies durch die Methode display_source(). Obwohl source zur Klasse Document gehört, wirken Veränderungen in source nicht automatisch auf root und view - aus Performance-Gründen werden Veränderungen nur auf Anforderung durch den Anwender geparst (per Kommando <Parse> des "Categories text view"-Fensters).

Das Speichern eines Baums in einer Datei mit der Methode save() beschränkt sich weitgehend auf den bei Klasse Node - Umwandlung in einen String beschriebenen Vorgang. Erwähnenswert ist, daß dabei kein neues Objekt der Klasse AOTreeString erzeugt wird, sondern ein einfacher String verwendet wird . Dies reicht vollkommen aus. Wünscht der Anwender eine Aktualisierung von source, so kann er dies mit dem <Refresh>-Kommando des "Categories text view" erreichen, daß display_source() aufruft.

Laden, Speichern und Editieren von Selektionen:

Mit dem Laden einer Selektion durch load_selection() ist immer auch deren Aktivierung verbunden. Dieser Vorgang wird unter Klasse Node - Aktivierung und Deaktivierung geschildert. Wichtig ist, daß vor der Aktivierung der sogenannte "minimale Aktivierungszustand" hergestellt wird. Dazu wird für root die Methode reset_activation() aufgerufen, welche die Auswahl bei allen Oder-Knoten löscht. Der ApplicationFrame-Befehl <Selection>-<Merge> bewirkt, daß dieser Schritt übersprungen wird. Die zum aktuellen Aktivierungszustand des Baums root passende Selektion wird mit der Node-Methode get_selection() erfragt (siehe Klasse Node - Aktivierung und Deaktivierung) und mit save_selection() in einer Datei gespeichert. Ähnlich wie der Baum-Quelltext kann auch diese aktuelle Selektion in einem Extra-Fenster ("Selection text view") als Text angezeigt und editiert werden (Methode display_selection()). Im Unterschied zum Baum-Quelltext wird der Inhalt dieses Fensters aber automatisch aktualisiert (durch Aufruf von refresh_selection_text() jedesmal wenn der Anwender eine Kante (de-)aktiviert oder eine Selektion lädt). Daher ist es auch nicht notwendig, die Selektion in einer Membervariable von Document zu sichern.

Operationen auf das editable object:

Es handelt sich um Editiervorgänge, die sich aus den Grundoperationen "paste" und "delete" zusammensetzen und das Clipboard (ein eigenständiges Dokument) zum Zwischenspeichern von Knoten verwenden. "Paste" kopiert einen Knoten (hier im Sinne eines Objekts der Klasse Node, also einschließlich der zulaufenden Kante) von einem Dokument in ein anderes. Der <Cut>-Befehl des ApplicationFrame-Menüs setzt sich z.B. aus <Copy> und delete zusammen. Das <Copy>-Kommando wiederum ist eine Paste-Operation vom Clipboard zum Hauptdokument. Paste selbst besteht im wesentlichen aus einem Aufruf der Node-Methoden add_child() und clone() (siehe Klasse Node - Kopie). Fast alle Editieroperationen haben gemein, daß sich der root-Knoten selbst nicht entfernen läßt, weil der sich dadurch ergebende Zustand nicht definiert ist (es bedürfte einer großen Anzahl lästiger Fallunterscheidungen um dem abzuhelfen). Ausnahme ist die Methode replace(), die sich auch auf den Wurzelknoten anwenden läßt. Sie wird durch den Anwender-Befehl <Replace> aufgerufen und aus dem "Properties"-Dialog, wenn dort die Klasse eines Knotens geändert werden soll ("Cast"), was in Java nur durch eine Knoten-Austausch sauber realisierbar ist. Vor allen Editieroperationen wird der komplette Baum kopiert und in der Membervariable undo_root gesichert (Methode prepare_undo()). Der <Undo>-Befehl des ApplicationFrame stellt den undo_root wieder her (Methode undo()). prepare_undo() wird nicht aus der Klasse Document heraus aufgerufen, sondern schon im Ereignishandler handleEvent() der Klasse ApplicationFrame, um die Erzeugung eines Baum-Backups nur bei Verwendung der betroffenen Funktionen durch den Anwender durchzuführen. Eine Erweiterungsmöglichkeit wäre ein mehrstufiges Undo mit einem Array undo_root[].


Die Klassen AOT und ApplicationFrame -> class AOT -> class ApplicationFrame

AOT, abgeleitet von applet.Applet und damit auch von Panel, ist die öffentliche Klasse des Programms. Von dieser Klasse wird beim Programmstart genau eine Instanz AOT.Aot erzeugt. Dies geschieht entweder durch den Browser oder durch die Prozedur main(), falls das Programm "stand-alone" abläuft. In diesem Fall fehlt natürlich der Applet-Kontext (d.h. die HTML-Seite). Läuft das Programm als echtes Applet, so wird das Applet-Panel in die HTML-Seite eingebettet. Das Dokument (bzw. dessen View) ist dann anfangs Komponente des Applet-Panels oder, auf Befehl des Anwenders, eines eigenen ApplicationFrame. Als Standalone-Anwendung befindet sich das Dokument anfangs in einem kleinen Applet-Simulator (das Frame aotframe der Klasse AOTFrame) oder wiederum im ApplicationFrame. Beim Start des Applets ruft der Browser die Methoden init() (beim Laden der umgebenden HTML-Seite) und start() (vor jeder neuen Anzeige des Applets). start() startet auch den (einzigen) Thread des Programms. Im Standalone-Fall übernimmt main() den Aufruf dieser Ereignis-Handler . Außerdem parst main() durch Aufruf der Methode GetParameters() die Kommandozeile und trägt die einzelnen Parameter in die Variablen m_param1.. ein. Wird init() vom Browser aufgerufen, geschieht etwa dasselbe, nur werden hier die Parameter aus dem HTML-Aufruf des Applets verwendet. In beiden Fällen handelt es sich um benannte, optionale Parameter (siehe Invocation). init() erzeugt außerdem das Haupt-Dokument (Membervariable document) und integriert sein View-Panel als "Component" in das Applet-Panel (Methode insert_document()). Weiterhin erzeugt init() das Clipboard (ohne dessen View schon mit show() tatsächlich anzuzeigen) und lädt eine Selektion. Ins Applet-Panel wird außerdem ein Button aufgenommen, der es dem Anwender ermöglicht, das Hauptdokument aus dem Applet-Panel herausz u lösen und in einem Fenster der Klasse ApplicationFrame darzustellen (Methode detach_document()). Derselbe Knopf, oder das Schließen des ApplicationFrame, bewirkt auch die Umkehrung dieses Vorgangs. Wird beim Programmaufruf das Flag frame_mode auf true gesetzt, so wird detach_document () bereits in init() automatisch aufgerufen. Neben den genannten Methoden enthält die Klasse AOT noch einige elementare, statische Funktionen zum Dateihandling (inquire_filename(), read() und save()). Alle Methoden operieren mit URLs und Dateinamen gleichermaßen. Unter Java 1.0 funktioniert das implizite PUT-Kommando im HTTP-Protokoll leider nicht, daher ist einem Applet nur das Laden von Dateien, nicht aber das Speichern möglich. Selbstverständlich kann ein Applet nur auf Host-URLs zugreifen. AOT.class muß aus diesem Grund im selben Verzeichnis wie die den Applet-/Embedded-Tag enthaltende HTML-Seite stehen.


Kommunikation mit dem Host per CATTP

Die Funktion Funktion communicate_with_host() in AOT stellt das einfache Protokoll CATTP (Categories Transfer Protocol) zur Verfügung, mittels dessen Daten zum Hostrechner (Server) geschickt (upload) und Antworten empfangen werden können (download). Die beiden korrespondierenden Anwendungskommandos sind <Selection>-<Send to host> (Datenbank-Eintrag) und <Selection>-<Request completion> (Datenbank-Abfrage). CATTP nutzt die Java-net Socket-Klassen und setzt somit auf TCP/IP auf. Weil die Übertragung nicht einfach über Datagramme erfolgt, sondern mit den aufwendigeren "reliable sockets", ist eine gewisse Übertragungssicherheit gewährleistet. Die Host-URL wird automatisch festgestellt (befindet sich Categories nicht im Applet-Modus, kann ein "Host" mit dem Kommandozeilen-Parameter <Host> erzwungen werden. Dort kann auch die Port-Voreinstellung 900 geändert werden, siehe Invocation). Das Protokoll ist so gestaltet, daß es sich einfach erweitern läßt und auf Client-Seite durch eine Art Skript-Sprache umgesetzt werden kann. Dabei wird an communicate_with_host() eine Folge von Token- (z.B. PUT_SELECTION), Befehls- (z.Zt. nur PULL zum Downloaden von Daten) und Daten-Strings übergeben (siehe Document.send_selection() und Document.complete_selection()).
Das Skelett der Client-Implementation stammt aus [2].

Die Datenbankkomponenten auf dem Host-Rechner einschließlich des Servers stammen von Gerhard Hingerl (hingerl@sdm.de). Es handelt sich dabei um Prolog-Programme, die im Rahmen einer Diplomarbeit am Lehrstuhl von Prof. Bry an der Universität München entwickelt wurden [4].

CATTP 1.0: (">>>" gehört nicht ins Protokoll, sondern dient nur zur Hervorhebung der Übertragungsschritte)
Die Redundanzen im Protokoll ermöglichen eine uniforme und erweiterungsfähige Implementierung.

Transaktion "Senden einer Selektion an den Server":

*Client:
>>>PUT_SELECTION\n

*Server:
>>>OK\n
oder
>>>ERROR\n
>>>Fehlermeldung\n

*Client, falls kein Fehler vom Server gemeldet wurde:
>>>Anzahl der nun folgenden Daten-Bytes (ohne \n; Format: siehe readInt)
>>>Selektion (byteweise Übertragung (ASCII), mit zusätzlichem Punkt hinten)
*Server:
>>>OK\n (Problem, da keine Fehlerpruefung stattfindet!)
*Client:
>>>Anzahl der nun folgenden Daten-Bytes (ohne \n; Format: siehe readInt)
>>>Kommentarterm (byteweise Übertragung (ASCII), mit zusätzlichem Punkt hinten)

(der Kommentarterm entspricht der Selektion, allerdings steht für jeden Kanten-Identifikator ein „Kommentar“. Dieser ist der Inhalt des Knoten-Hilfetextes. Außerdem sind Teilbäume (insbesondere auch Textknoten), in denen kein Kommentar vorkommt, durch _ ersetzt)

*Server:
>>>OK\n
oder
>>>ERROR\n
>>>Fehlermeldung\n

*Client, in jedem Fall:
>>>CLOSE_CONNECTION\n (Server schließt jetzt seinen Socket)

Transaktion "Anfrage":

*Client:
>>>GET_COMPLETION\n

*Server:
>>>OK\n
oder
>>>ERROR\n
>>>Fehlermeldung\n

*Client, falls kein Fehler vom Server gemeldet wurde:
>>>Anzahl der nun folgenden Daten-Bytes (ohne \n; Format: siehe readInt)

>>>Partielle Selektion (byteweise Übertragung)

*Server:
>>>OK\n
>>>Anzahl der nun folgenden Daten-Bytes (ohne \n; Format: siehe writeInt)

>>>Daten (byteweise Übertragung (ASCII))

oder
>>>ERROR\n
>>>Fehlermeldung\n

*Client, falls kein Fehler vom Server gemeldet wurde:
>>>OK\n
oder
>>>ERROR\n
>>>Fehlermeldung\n (will der Server vielleicht irgendwo protokollieren)

*Client, immer:
>>>CLOSE_CONNECTION\n (Server schließt jetzt seinen Socket)

ApplicationFrame -> class ApplicationFrame

Wie geschildert, kann das Applet das Hauptdokument optional in ein eigenes Fenster der Klasse ApplicationFrame auslagern (definiert in AOT.java, AOT Member application). Es handelt sich dabei um ein Top-Level Fenster der AWT-Klasse Frame. Seine Aufgabe ist es, ein Pulldown-Menü zur Bedienung des Dokuments bereitzustellen. Zusätzlich fängt ApplicationFrame diverse Tastaturereignisse als Shortcuts für die Menübefehle ab. Wurde das Programm nicht im access mode 'Superuser' gestartet, sind Befehle zum Editieren des Dokuments verborgen. Zentrale Methode der Klasse ist der Ereignishandler handle_event(), der alle Ereignisse abfängt, die zu Funktionen führen, die auch über das Pulldown-Menü erreichbar wären. Dies bedeutet, daß auch Tastatur-Eregnisse oder Kommandos, die vom Kontextmenü ausgehen, eine zentrale Behandlung in handle_event() erfahren. Wenn das Programm einmal z.B. um eine Icon-Leiste erweitert wird, sollte dafür dieser Mechanismus beibehalten werden, da er die Software-Wartung erleichtert.


Bekannte Softwarefehler

siehe Known Bugs.

Bei der Meldung neuer Bugs an des Programm-Autor sollte neben einer genauen Fehlerbeschreibung (einschließlich aller Schritte, die zum Fehler geführt haben) möglichst auch der Inhalt der Standardausgabe (Stream out) vom Programmstart an angegeben werden (Netscape: <Options>-<Java console>, Explorer: Datei Windows\java\javalog.txt (vorher muß mit <View><Options><Advanced> die Protokollierung aktiviert worden sein. Dazu ist danach ein Neustart des Explorers notwendig)).


Literatur

[1] Heribert Schütz, Dietmar Zaefferer: Eine linguistische Wissensbank,
Institut für Informatik der Universität München (PMS-FB-1996-17), 1996.
URL:
http://www.informatik.uni-muenchen.de/publikationen/#PMS-FB-1996-17

[2] David Flanagan: Java in a Nutshell, O'Reilly&Associates, 1996.
URL: http://www.ora.com/catalog/books/javanut

[3] Stefan Münz:
HTML-Dateien selbst erstellen, 1997.
URL:
http://www.netzwelt.com/selfhtml

[4]
Gerhard Hingerl: Eine deduktive Datenbank für die Linguistik,
Institut für Informatik der Universität München (Diplomarbeit), 1996.
URL:

http://www.pms.informatik.uni-muenchen.de/ publikationen/diplomarbeiten/Gerhard.Hingerl/ausarbeitung.ps.gz


Contents Index