GGL100 - 2003 - docs - diplomarbeit.pdf

86
Portierung und Verfeinerung einer Graph- Bibliothek von C++ nach Java Diplomarbeit - korrigierte Fassung - März 2003 Carsten Winckler Betreuer: Dipl.-Informatiker Dr. Andreas Koch The Generic Graph Library for Java

Transcript of GGL100 - 2003 - docs - diplomarbeit.pdf

Portierung und Verfeinerung einer Graph-Bibliothek von C++ nach Java

Diplomarbeit- korrigierte Fassung -

März 2003

Carsten Winckler

Betreuer: Dipl.-Informatiker Dr. Andreas Koch

The Generic Graph Libraryfor Java

ii

Erklärung

iii

Erklärung

Hiermit erkläre ich, dass die vorliegende Arbeit selbständig nur unter Verwendung deraufgeführten Hilfsmittel und nur von mir selbst erstellt wurde. Eine Auflistung der verwendetenHilfsmittel findet sich in Anhang B "Verwendete Hilfsmittel".

Hannover, den 17.3.2003

iv

Vorwort

v

Vorwort

Für diese Arbeit habe ich viel Blut, Schweiß und Tränen geopfert, um ein Produkt zu erstellen, dasden professionellen Ansprüchen genügt. Es hat unzählige Iterationen durchlaufen, um es nachvielen Aspekten hin zu optimieren und damit der Praxis gerecht zu werden. Da es vom Charakterher universell genutzt werden kann, hoffe ich, das es entsprechend angenommen wird und somitden Aufwand rechtfertigt.

Ich widme diese Arbeit meiner Frau Anne-Kathrin, Herrn Prof. Dr. Ulrich Golze und Herrn Dr.Andreas Koch, die alle sehr viel Geduld mit mir hatten und somit diese Arbeit mit ermöglichthaben.

Über Feedback zufriedener Nutzer oder auch für die eine oder andere Frage [email protected] würde ich mich sehr freuen.

Hannover, März 2003 Carsten Winckler

vi

Inhalt

vii

Inhalt

1 Einleitung ..............................................................................................................1

2 Die Entwicklung der GGL .................................................................................52.1 Die Transformation LEDAs von C++ nach Java ............................................................................................62.1.1 Step 1 ...................................................................................................................................................................102.1.2 Step 2 ...................................................................................................................................................................112.1.3 Step 3 ...................................................................................................................................................................122.1.4 Zu Hilfsbibliotheken wie der JGL....................................................................................................................14

2.2 Optimierung und Ergänzung..............................................................................................................................152.2.1 Java Coding Conventions ................................................................................................................................16

3 Benutzerhandbuch...........................................................................................173.1 Einführung ..............................................................................................................................................................183.1.1 Dokumentation ...................................................................................................................................................183.1.2 Voraussetzungen................................................................................................................................................203.1.3 Die Distribution ...................................................................................................................................................203.1.4 Installation............................................................................................................................................................213.1.5 Übersetzen der Bibliothek................................................................................................................................223.1.6 Testen der Bibliothek.........................................................................................................................................233.1.7 Debuggen mit der Bibliothek ...........................................................................................................................233.1.8 Beispiele ausführen............................................................................................................................................23

3.2 Umfang der GGL...................................................................................................................................................243.2.1 Klassenstatistik...................................................................................................................................................253.2.2 Packages der GGL.............................................................................................................................................26

3.3 Allgemeines zur Implementierung....................................................................................................................283.3.1 Kompatibilität mit anderen Software-Bibliotheken .....................................................................................283.3.2 Programmierstil...................................................................................................................................................283.3.3 Ein-/Ausgabe.......................................................................................................................................................293.3.4 Randoms..............................................................................................................................................................293.3.5 Debuggen ............................................................................................................................................................30

3.4 Generisches Programmieren mit Type ...........................................................................................................313.4.1 Das generische Konzept...................................................................................................................................313.4.2 In der Anwendung..............................................................................................................................................333.4.3 Die Klasse Type..................................................................................................................................................363.4.4 Typklassen...........................................................................................................................................................373.4.5 Instantiierung von Typklassen .........................................................................................................................403.4.6 Generische Methoden.......................................................................................................................................413.4.7 Generische Klassen ...........................................................................................................................................433.4.8 Die Spezialisierung von Klassen .....................................................................................................................46

3.5 Datentypen und Algorithmen.............................................................................................................................483.5.1 Basisdatentypen.................................................................................................................................................483.5.2 Die Klasse Graph................................................................................................................................................493.5.3 Graph-Algorithmen ............................................................................................................................................52

Inhalt

viii

3.6 Iteratoren................................................................................................................................................................523.6.1 Beispiel-Iteration mit Graph.............................................................................................................................533.6.2 Sichere Iteratoren ..............................................................................................................................................55

3.7 Das Testsystem....................................................................................................................................................56

3.8 Performance..........................................................................................................................................................56

4 Zusammenfassung und Ausblick.................................................................59

5 Literatur...............................................................................................................635.1 Literatur zu LEDA, Java und C++ ......................................................................................................................635.2 Primärliteratur zu den Graph-Algorithmen.......................................................................................................64

Anhang A Klassen der GGL ..............................................................................67A.1 Package ggl.basic ...............................................................................................................................................67

A.2 Package ggl.graph ..............................................................................................................................................68

A.3 Package ggl.graphAlg ........................................................................................................................................69

A.4 Package ggl.graphIO..........................................................................................................................................70

A.5 Package ggl.util....................................................................................................................................................70

A.6 Package ggl.type.................................................................................................................................................71

A.7 Package ggl.inst ..................................................................................................................................................73

A.8 Package ggl.test ..................................................................................................................................................75

Anhang B Verwendete Hilfsmittel....................................................................77B.1 Verwendete Hardware (inkl. OS)......................................................................................................................77

B.2 Verwendete Programme....................................................................................................................................77

B.3 Verwendeter Quellcode .....................................................................................................................................78

B.4 Hauptsächlich verwendete Literatur ..............................................................................................................78

Einleitung

1

1 Einleitung

Das Thema dieser Arbeit ist die Entwicklung und Vorstellung einer aufgenerische Programmierung, Basis- und Graphdatentypen sowie Graph-Algorithmen

ausgerichteten Java-Bibliothek.

Die hier vorgestellte Bibliothek trägt den Namen Generic Graph Library for Java, kurz GGL. Siebasiert auf einer Portierung weiter Teile der renommierten C++-Klassenbibliothek LEDA (Libraryof efficient data types and algorithms), die auf effiziente Datentypen und Algorithmen ausgelegtist. Genauer gesagt basiert sie auf der Version 3.8a der LEDA-Bibliothek aus dem Jahre 1999. Alledort enthaltenen graphrelevanten Datentypen und Algorithmen sowie alle dafür notwendigenBasisdatentypen und Algorithmen wurden nach Java portiert und anschließend verfeinert. DieVerfeinerung schloss das Beheben von Fehlern, das konsequente Ausrichten auf Java, die Ergän-zung um ein Konzept und Mittel zur generischen Programmierung, ein integriertes Testverfahrenund die Erweiterung um zusätzliche Klassen und Methoden ein, um nur die wichtigsten Aspektezu nennen.

LEDA wurde ursprünglich von einer Projektgruppe des Saarbrücker Max-Planck-Instituts fürInformatik1 entwickelt und wird seit 1995 kommerziell über die eigens dafür gegründete LEDASoftware GmbH, die heute den Namen Algorithmic Solutions Software GmbH2 trägt, vertrieben.Seit dem 1. Februar 2001 hat das Max-Planck-Institut das Zepter an die Algorithmic SolutionsSoftware GmbH gänzlich übergeben, d. h., seitdem trägt diese auch die Verantwortung fürWartung und Weiterentwicklung.

Für Forschung und Lehre war es früher möglich, eine kostenfreie Forschungslizenz direkt über dasMax-Planck-Institut zu erwerben. Diese Möglichkeit packte ich damals am Schopf und befindemich seitdem in ihrem Besitz. Sie wurde für diese Arbeit genutzt und impliziert daher für die GGLals Ganzes, dass auch sie nicht kommerziell eingesetzt oder vertrieben werden darf, sondern alleinigder Forschung und Lehre verhaftet bleibt. Die Teile und Ideen der GGL, die ihren Ursprung nichtin der LEDA-Bibliothek, sondern vielmehr in dem Entstehungsprozess der GGL haben, wie z. B.das Konzept und Framework zur generischen Programmierung mit Java, stelle ich, CarstenWinckler, hiermit für meinen Teil in die Public Domain.

Warum diese Arbeit?

Das Ziel dieser Arbeit war es, eine universelle Bibliothek mit Graphdatentypen und -algorithmenfür Java zu erstellen, um die Entwicklungen der Abteilung Entwurf Integrierte Schaltungen(EIS) der Technischen Universität Braunschweig zu fördern. Es ging hierbei in erster Linie umden Einsatz von Java, da Programme in dieser Sprache erfahrungsgemäß wartungsfreier und leich-ter zu erstellen und zu verstehen sind. Durch die Vielzahl verfügbarer Java Virtual Machines istman mit Java zudem weitestgehend plattformunabhängig. Die Vorteile von Java gehen zwar teil-weise zu Lasten der Laufzeit der Programme, doch wird dies für die meisten Softwareprojekte in

1 Siehe http://www.mpi-sb.mpg.de/LEDA2 Siehe http://www.algorithmic-solutions.de/

Einleitung

2

Anbetracht der Performance heutiger Computer gerne in Kauf genommen, wenn die Entwick-lungszeit dafür rapide abnimmt.

Graphdatentypen und -algorithmen sind wesentliche Bestandteile von Softwareprojekten imBereich VLSI. Die GGL ist, meiner Meinung nach, durch die Menge qualitativ hochwertigerDatenstrukturen und Algorithmen samt der generischen Ausrichtung bestens geeignet, sich alsStandardbibliothek für Softwareprojekte am Institut zu etablieren. Die Vorteile liegen auf derHand. Anstatt sich mit Problemen zu beschäftigen, die bereits gelöst sind, kann sich der Entwick-ler voll auf seine Kernaufgaben konzentrieren. Die Teile, die er von der Bibliothek benötigt, kanner sofort verwenden, indem er die Bibliothek in sein Softwareprojekt einbindet, ohne negativeSeiteneffekte erwarten zu müssen. Die GGL liefert ihm algorithmisches Know-how für seineGraphen- und Netzwerkprobleme samt zugehöriger Datentypen frei Haus. Sie eignet sich durchdie Menge an grundlegenden Algorithmen und generischen Datenstrukturen wie Sortieren undSuchen, Keller, Schlangen, Listen, Bäume und Wörterbücher, aber auch für fast alle anderenSoftwareprojekte.

Die Bibliothek LEDA findet beispielsweise neben dem VLSI-Sektor auch Anwendung in der Tele-kommunikationsbranche, Verkehrsplanung, Bioinformatik, Medizin und Computergrafik undwird von renommierten Unternehmen wie Hewlett Packard, France Télécom und IBM eingesetzt.Auch wenn bei der Portierung die Module "geometrisches Rechnen" und "GUI" ausgeklammertwurden, so spricht nichts dagegen, dass sich die GGL ebenfalls in oben genannten Bereichen ein-setzen lässt.

Die GGL ist, obwohl sie auf der Version 3.8a der LEDA-Bibliothek basiert, auf der Höhe der Zeit.Zwar befindet sich LEDA derzeit in der Version 4.4, doch wurden an den graphrelevantenBestandteilen nur marginale Veränderungen vorgenommen1. Was den anderen Schwerpunkt dieserArbeit angeht, die Erstellung eines Konzepts und Frameworks für die generische Programmierungmit Java, so ist seitens der Urheber zwar eine entsprechende Spracherweiterung geplant, aber beiweitem noch nicht umgesetzt.

Allgemeines zur Portierung

Die GGL wurde in einem intensiven Entwicklungsprozess erstellt und hat dabei unzähligeIterationen zur Optimierung durchlaufen. Das Augenmerk richtete sich hierbei auf korrekteFunktionalität, hohe Performance, die Verbesserung und Ergänzung der Datentypen, die stetigeVerfeinerung des enthaltenen Konzepts zur generischen Programmierung und einen insgesamtguten Java-Programmierstil unter Einhaltung strikter Java Coding Conventions.

Es wurde zudem sehr viel Arbeit in die API-Dokumentation investiert, weit mehr als in das vorlie-gende Dokument. Die API-Dokumentation umfasst mehr als 1000 Din A4 Seiten und enthältunter anderem den angepassten Inhalt des LEDA Manuals. Jede nach außen sichtbare Klasse undMethode ist dokumentiert.

Die GGL ist überall sofort lauffähig, da sie alleinig auf der Java-Standardbibliothek basiert. Sie istmit JDK 1.1 bis zur heutigen Version 1.4.1 auf Kompatibilität getestet. Sie ist derzeit nicht aufMultithreading ausgelegt, doch wurde bewusst die Verwendung gemeinsamer Speicherbereichezwischen Klassen oder Methoden, die nicht direkt zu einem Datentyp gehören, vermieden. Eine

1 Siehe http://www.algorithmic-solutions.de/ledaChangesMitte.htm

Einleitung

3

Ergänzung der Bibliothek um diese Eigenschaft ist daher im Prinzip recht einfach, nur der Umfangdes Quellcodes stellt die Hürde dar. Dieser liegt bei ungefähr 2100 Kilobyte, aufgeteilt auf 212Dateien/Klassen, wobei zu beachten ist, dass die API-Dokumentation ebenfalls integriert ist.

Dokumentation

Wie eben schon erwähnt, ist das vorliegende Dokument nicht die einzige Dokumentation zurGGL. Es ist als Einstieg und zur Erläuterung übergeordneter Themen gedacht. Die einzelnenKlassen und Algorithmen der GGL werden über den der GGL beigefügten GGL API Guide[Win03] detailliert dokumentiert. Der Quellcode selbst ist ebenfalls ein Teil der Dokumentation,zum Beispiel um zu sehen, wie mit der GGL generische Klassen und Methoden erzeugt werdenoder um einen Algorithmus genauer zu studieren. Durch eine sehr sorgfältig ausgeführte Struktu-rierung, einheitliche und einleuchtende Namensgebungen, ist dieser leicht zu lesen und nichtzuletzt durch die vielen zusätzlichen Kommentare auch leicht zu verstehen. Er ist prädestiniert fürdie Wiederverwendung in eigenen Programmen.

Als weitere wichtige Quelle können die LEDA-Dokumentationen betrachtet werden. Da dasLEDA Manual [LUM99] bereits in die API-Dokumentation eingebunden ist, bleiben noch einigeBücher und der LEDA Guide [LUM03], um die Graph-Algorithmen besser kennenzulernen. Siesind alle im Literaturindex mitaufgeführt, genauso wie die Quellen zur Primärliteratur vieler in dieGGL integrierter Graph-Algorithmen.

Nun aber zu dieser Dokumentation. Diese Dokumentation gliedert sich im wesentlichen in diezwei Hauptteile "Die Entwicklung der GGL" und "Benutzerhandbuch". Obwohl dies fastselbsterklärend ist, hier noch ein paar Worte dazu:

Das Kapitel 2 "Die Entwicklung der GGL" beschreibt den gesamten Entwicklungsprozess derGGL und ist für diejenigen interessant, die ebenfalls vorhaben, C++-Code nach Java zu transfor-mieren. Es wird hierbei auch auf Umsetzungsregeln für einzelne C++-Sprachelemente eingegangen,doch würde die vollständige Auflistung den Rahmen dieser Arbeit sprengen. Trotz alledem ist diessicher ein interessantes Kapitel.

Das Kapitel 3 "Benutzerhandbuch" kann als Buch im Buche aufgefasst werden. Es führt denBenutzer langsam aber sicher an die GGL heran und beschreibt alle zur Nutzung der GGL wichti-gen Themen. Es enthält eine eigene Einführung zur GGL, die als Ergänzung zu dieser betrachtetwerden kann. Jeder, der vorhat mit der GGL zu arbeiten, sollte dieses Kapitel gründlich lesen, umauf einfache Art und Weise und in relativ kurzer Zeit ein tiefes Verständnis für die GGL zu ent-wickeln und produktiv mit ihr zu arbeiten. Der Leser wird hierbei Schritt für Schritt und mithilfeeiniger Beispiele an die wesentlichen Bestandteile der GGL herangeführt.

Nach dem Kapitel 4, das eine wichtige Zusammenfassung und einen Ausblick enthält, und demKapitel 5 mit dem Literaturindex, folgt der Anhang. Der Anhang A enthält eine kompletteAuflistung aller Klassen samt Kurzbeschreibung sortiert nach Packages, der Anhang B eineAuflistung der verwendeten Hilfsmittel.

Um Missverständnissen vorzubeugen: Des öfteren liest man in dieser Dokumentation das Wort"wir". Zum Beispiel wenn es um Vereinbarungen geht, wie etwas zu verstehen ist oder wie etwasbenannt wird. Mit "wir" sind der Leser und der Autor dieser Arbeit gemeint - in stillerÜbereinkunft.

4

Die Entwicklung der GGL

5

2 Die Entwicklung der GGL

Dieses Kapitel beschreibt, wie der Name schon sagt, die Entwicklung der GGL. Wie bereits in derEinleitung zu lesen, handelt es sich hierbei um die Portierung der LEDA-Bibliothek in der Version3.8a von C++ nach Java. Die Entwicklung gliederte sich in zwei grobe Teile: 1. dieTransformation nach Java und 2. die Optimierung und Ergänzung.

Beide Teile hatten ungefähr den gleichen Aufwand und werden in den beiden gleich folgendenUnterkapiteln erläutert. Zuerst aber ein paar Worte zu der verwendeten Hard- und Software undden ersten Problemen, die zu lösen waren.

Als Computerplattform für diese Arbeit wurde ein Apple Macintosh kompatibler Computer mitdem Betriebssystem Mac OS 8.6 eingesetzt. Als Entwicklungsumgebung wurde das ProduktCodeWarrior Pro 5.0 der Firma Metrowerks1 verwendet, das sich sowohl für C++ als auch für Javasehr gut eignet. Mit ihr war es möglich, Java- und C++-Projekte parallel anzulegen und zu verwal-ten. Das "Hüpfen" zwischen den Projekten gelang auf Knopfdruck und die Volltextsuche nachWörtern oder Mustern in Form von regulären Ausdrücken war einfach und sehr schnell. Mithilfevon Werkzeugen wie Keyquencer2, Quickeys3 und AppleScript4 ließen sich die kompliziertestenRoutinevorgänge hervorragend automatisieren. Zur weiteren Beschleunigung wurde eine RAM-Disk angelegt, die die Kompiliervorgänge sowie die Such- und Ersetzungsoperationen zusätzlichbeschleunigte.

Das, was in großen Unternehmen über Werkzeuge für Change und Configuration Managementerreicht wird, wurde in meinem 1-Personen-Entwicklerteam über eine Menge kleinerselbstgestrickter Programme erlangt. Dies war natürlich so nur möglich, da ich mich mit anderenEntwicklern nicht synchronisieren musste. Beispielsweise wurde eine Konfiguration eines Projektsauf einfache Weise abgelegt, indem der ganze übergeordnete Ordner, versehen mit einer laufendenNummerierung, dem Datum und weiteren Kommentaren halbautomatisch in ein Archivverzeich-nis kopiert wurde. Entdeckte ich einen falschen Weg in der Transformation eingeschlagen zuhaben, so ging ich soweit zurück, bis ich wieder an einem stabilen Ausgangpunkt angelangt war.Dieses Backtracking-Verfahren musste ich aufgrund falscher Teiltransformationen, z. B. bedingtdurch zu rigorose Textersetzungen, einige Male anwenden. Die Änderungen zwischen verschie-denen Konfigurationen ließen sich mithilfe des mächtigen Texteditors BBEdit 6.05 hervorragendnachverfolgen. So konnten die kompletten Ordner in einem Schritt auf unterschiedliche Inhalteinnerhalb der enthaltenen Dateien überprüft werden. Die Ergebnisse lagen jeweils innerhalb vonwenigen Sekunden vor.

LEDA auf meinem Mac zum Laufen zu bringen, stellte sich als die erste große Hürde dar. Zumeinen musste ich ein UNIX-System verwenden, um die Archivdatei mit dem LEDA-Quellcode zuentschlüsseln und auszupacken. Gesagt, getan, setzte ich ein solches auf meinem Mac auf. Das 1 Siehe www.metrowerks.com2 Siehe www.binarysoft.com3 Siehe www.cesoft.com4 Teil des Mac OS seit Version 7.0 / Siehe www.apple.com5 Siehe www.barebones.com

Die Entwicklung der GGL

6

Dekodieren mit dem vom Max-Planck-Institut an mich gesendeten Lizenzschlüssel ging dann auchauf Anhieb, doch die nächste Hürde begann mit den Feinheiten unterschiedlicher C++-Schnitt-stellen, die zwischen verschiedenen Plattformen existieren. LEDA ist nämlich mitnichten so ausge-richtet, dass es auf einer von allen C++-Schnittstellen gebildeten Schnittmenge operiert und damitüberall leicht zu kompilieren ist. Im Gegenteil. LEDA setzt auf stark instrumentierten Code, derüber den C-Präprozessor der Ziel-Plattform angepasst wird.

Weder mein Unix-Derivat noch mein Mac-Betriebssystem kamen auf Anhieb damit zurecht. Siegehörten und gehören bis heute nicht zu der Menge der von LEDA unterstützten Betriebssysteme.Auch ein Versuch mit Mac OS X 10.2.2 auf einem Apple G4 Powerbook mit der LEDA 3.8aschlug fehl, obwohl ich davon ausgehe, dass diese Plattform von der aktuellen Version 4.4 besserunterstützt wird.

Die Schritte, die dann als nächstes kamen, kann man als "vorsichtiges Herantasten" bezeichnen.Ich versuchte zuerst einmal LEDA mit den Einstellungen für die anderen Zielplattformen zu über-setzen, jedoch leider ohne Erfolg. Am Ende stellte sich heraus, dass ich meine eigene C++-Standardbibliothek verändern musste, um LEDA kompilieren zu können .

2.1 Die Transformation LEDAs von C++ nach Java

Um eine sichere Transformation gewährleisten zu können, wurden insgesamt 4 Projekte angelegt.Es handelte sich um 3 C++- und ein Java-Projekt, die alle parallel in der CodeWarrior IDE ange-legt werden konnten. Die Vorgehensweise war die, dass ausgehend von der veränderten, zumLaufen gebrachten Originalvariante in zwei Schritten der C++-Code gen Java modifiziert wurde,um dann mit der stark vereinfachten Variante auf Java zu zielen.

ProjektLEDA ORIG

C++

ProjektLEDA MOD1

C++

ProjektLEDA MOD2

C++

ProjektGGLJAVA

- Der Transformationsprozess -

Einfach nur 4 Transformationsschritte durchzuführen (inkl. der Anpassung LEDAs an die Mac-Plattform) ist sicher noch keine Lösung für eine sichere Transformation. Ein Testverfahren mussteentwickelt werden, um die korrekte Abbildung zu garantieren. Angefangen wurde damit, sämtlicheBeispiele, die LEDA mitgegeben wurden, ablaufen zu lassen und einen Textvergleich der Ausgabeüber stdout durchzuführen.

Dies war aber bei weitem noch nicht ausreichend, da sich die Testprogramme meist nicht deter-ministisch verhielten. Dies lag an den in ihnen verwendeten Zufallswerten. Gerade die Graph-generatoren machten von ihnen großen Gebrauch.

Die Entwicklung der GGL

7

Die Funktionen für Zufallswerte mussten also so instrumentiert werden, dass sie auf Wunsch einevorher festgelegte Sequenz von "Zufallswerten" produzieren. Diese Änderungen mussten an allenParallelprojekten durchgeführt werden, um die Tests untereinander austauschen zu können. Damitist gemeint, dass ein Projekt eine Ausgabe und eine Sequenz von Zufallswerten produziert, die voneinem anderen Projekt konsumiert wird.

So kam man an manchen Stellen mit "Hausmitteln" wie dem C++-Debugger einfach nicht mehrweiter. Die Möglichkeit des Parallel-Debuggens seitens CodeWarrior war zwar gegeben und wurdeauch intensiv genutzt, auch bei Vergleichen der letzten Transformationsstufe im Falle einesFehlers, doch waren die Menge der Schritte und vor allem die mangelhafte Debug-Fähigkeit inC++-Templates bzw. in dem für den C-Präprozessor gestalteten Code das riesengroße Hindernis.

Hierzu ein paar Erläuterungen: Unter dem Parallel-Debuggen versteht man, zwei Projekte syn-chron zu überprüfen. Das heißt, ausgehend von einem Startpunkt, werden in beiden Programmensynchron die gleichen Schrittfolgen durchlaufen, wobei man davon ausgeht, dass sich das einekorrekt verhält und das andere Fehler hat.

Bei C++-Template-Instantiierungen vergibt der Compiler oft eigene Namen oder produziert über-haupt keine Debug-Information. Dies bedeutet, man kann in die Templates nicht hineinsehenoder muss sehr aufwendig jedes Datum einzeln interpretieren. Bei der von LEDA eingesetztenselbstgebauten Speicherverwaltung und der exzessiven Ausnutzung des C-Präprozessors, z. B. füralle Iteratoren, ist ein ständiges Casting (hiermit ist der manuelle Einsatz der richtigen Cast-Operatoren bei der Betrachtung der Variablenwerte im Debugger gemeint) so aufwendig undproblembehaftet, dass man schnell wieder anfängt, auf die guten alten printf-Funktionen zurück-zugreifen. Das bedeutet, man instrumentiert den Code für die Ausgabe von Variableninhalten undAblaufinformationen.

Es gab Probleme, bei denen ich nicht umhin kam, den Code so zu instrumentieren, dass mirsogenannte Call-Listen ausgegeben wurden, die mir genau anzeigten, welche Konstruktoren,Destruktoren und Funktionen durchlaufen wurden. Gerade bei sporadischen Fehlern, die z. B. nurbei einem von zehn oder einem von 100 Testszenarios auftraten, musste ich mir etwas einfallenlassen, um den Fehler rekapitulieren zu können. Die massive Instrumentierung des Codes, diedafür nötig war, wurde aufgrund des manuellen Aufwands zwar halbautomatisch absolviert, dochwar diese "Holzhammermethode" trotz allem immer mit sehr viel Aufwand verbunden und wurdeerst als letztes Mittel verwendet.

Da darauf geachtet wurde, dass ein möglicher Nicht-Determinismus nur noch von den Random-Funktionen bestimmt war und nicht von irgendwelchen Zeitangaben oder -messungen, musstenalso lediglich alle Zufallswerte protokollierbar sein und die Funktion für die Erstellung derZufallswerte zentral verwaltet und instrumentiert werden. Und zwar so, dass sie auch eine vorherprotokollierte Abfolge wiedergeben konnten.

Für einen Testlauf mussten demnach letztlich zwei Dinge möglich sein, um mit möglichst vielenZufallsszenarien eine hohe Testabdeckung zu erreichen und im Falle eines Fehlers die Ursacherekapitulieren zu können: Erstens mit zufällig bestimmten Szenarien ausgeführt werden zukönnen, wobei alle Testergebnisse und die Folge der Zufallswerte protokolliert werden. Undzweitens müssen diese unter Benutzung der zuvor protokollierten Zufallswerte wiederholbar sein,um die Ergebnisse mit den zuvor aufgezeichneten Ergebnissen auf Identität überprüfen zu können.

Es ergab sich also folgendes Testsystem:

Die Entwicklung der GGL

8

ProjektLEDA ORIG

C++instrumentiert

ProjektLEDA MOD1

C++instrumentiert

ProjektLEDA MOD2

C++instrumentiert

ProjektGGLJAVA

instrumentiert

RandomsFiles for each

single test

- Das Testsystem -

Zugegeben, dies ist eine sehr abstrakte Darstellung, da nicht gezeigt wird, wie die Tests ausgeführtwerden, noch die Ergebnisse mit denen anderer Stufen verglichen werden, um einen Fehler zudiagnostizieren.

Dieses Testverfahren wird auch Regressionstest genannt, d. h., es wird überprüft, ob ein Systemaufgrund seiner Weiterentwicklung seine alte Funktionalität immer noch bewahrt hat. Dies wirdnatürlich nicht mit einem einzigen Testszenario erreicht, sondern mit einer Fülle derselben. Imbesten Fall kann man auch Produkte einsetzen, wie ich es versucht habe, um die Code-Über-deckung zu bestimmen und damit erkennen, ob ein Test eine Routine vollständig getestet hat.Dies ist die Menge an Code-Zeilen bzw. an Funktionen und Verzweigungen, die im Vergleich zurGesamtmenge bei dem Test durchlaufen wurde. Allerdings kam das Produkt Telelogic LogiscopeTestChecker1, das ich ausprobierte, mit der Menge an Code nicht zurecht - es war leider nur fürden Embedded-Sektor geeignet.

Der Regressionstest war für das hier vorgestellte Projekt der Schlüssel zum Erfolg. Die vier Stufendienten dazu, in definierten Schritten zum Ziel zu gelangen und nicht die Orientierung zu verlie-ren. Wurden die Tests zweier Stufen weiter entwickelt, dann wurde meist erst bei einem Fehlerdiese Entwicklung auch auf die vorherige Stufe übertragen, um zu überprüfen, dass der Fehler nurdurch die letzte Stufe produziert wurde und nicht schon aus der vorherigen Stufe "mitgeschleift"wurde.

Die Vergleiche der Testergebnisse mit der Java-Stufe waren ein anderes problematisches Thema.So musste die Ausgabe für alle nativen Datentypen angepasst werden, d. h., es wurden Methodenentwickelt, die Namen wie "printLikeC" hatten. Am problematischsten war die Zahlengenauigkeitvon Fließkommazahlen, da sie manchmal unvorhergesehen variierte. Es musste beispielsweise auch

1 Siehe www.telelogic.com

Die Entwicklung der GGL

9

die Ausgabe von boolean-Werten in der Testausgabe in Form von "0" und "1" passieren, anstatt inForm von "true" und "false".

Um es vorwegzunehmen: Von dem Vergleich der anfänglichen Ausgabe über stdout wurde schnellAbstand genommen. Stattdessen wurden die Tests mit Funktionen ausgestattet, die die Ergebnissein automatisch erzeugte Dateien einfließen ließen. Auch wurde nach erfolgreicher Transformationdie Instrumentierung für die Call-Listen-Erzeugung wieder entfernt, die eher für das ganz harteDebuggen bzw. für die Erkenntnis der stark verschleierten Abläufe in LEDA diente. Und was auchwichtig ist, die Seile zwischen den Projekten wurden aufgrund des Aufwands stetig gelockert. Dasheißt, nachdem ein Folgeprojekt (LEDA MOD1, LEDA MOD2, GGL) als korrekt tituliertwurde, wurden die vorherige Stufen nicht mehr verändert. Aber wie man es kennt, entdeckt mandann doch noch den einen oder anderen Fehler oder ist sich bei einer bestimmten Umsetzungs-regel im Unklaren, ob sie vielleicht nicht zu schwach war.

In diesem Fall musste das vorherige Projekt wieder aktiviert und die Testfiles und die Ausgabeangeglichen werden. Dann drehte sich der Spieß also um, d. h., die Ausgabemethoden wurden vonder nachfolgenden Stufe übernommen, bekamen also immer mehr GGL-Style:

ProjektLEDA ORIG

C++instrumentiert

ProjektLEDA MOD1

C++instrumentiert

ProjektLEDA MOD2

C++instrumentiert

ProjektGGLJAVA

instrumentiert

RandomsFiles for each

single test

- Zwei Schritte vor, einen zurück -

Man erkennt, dass das Ursprungsprojekt LEDA ORIG keinen rückwärts gerichteten Pfeil bekom-men hat. Dies hat 2 Gründe: zum einen sollte die Ursprungsversion weitestgehend konserviertbleiben, um im Zweifelsfall nicht den Boden unter den Füßen zu verlieren. Zum anderen ist dererste Transformationsschritt eher trivial gewesen im Vergleich zu den andern. Und damit sind wirauch gleich beim letzten Thema diese Abschnitts. Was passierte eigentlich in den einzelnenSchritten?

Wie schon erwähnt, hat die LEDA-Bibliothek aufgrund ihrer plattformbezogenen Optimierung,der Art wie Iteratoren und andere Mechanismen umgesetzt sind und nicht zuletzt durch dieselbstgestrickte Speicherverwaltung, die Adresswerte so umbiegt, dass sie für die nativen

Die Entwicklung der GGL

10

Datentypen int, long, char, double, etc. direkt genutzt werden, um eine Indirektion und ein paarBytes zu sparen, in anderer Hinsicht große Defizite.

Vor allem hat die Transparenz und nicht zuletzt die Wartbarkeit dadurch stark gelitten. Durch dievielen Makros und Code-Tricks seitens LEDAs verliert man den Überblick über die Vorgänge. Diegroben Vorgänge sind zwar gut und kompakt dargestellt, doch die vielen Seiteneffekte, die durchMakros, Operator-Overloading, Adressentricks und virtual an vielen Stellen entstehen, sieht mannicht und kann man nur mit sehr großem Aufwand nachvollziehen.

Das Debuggen kann man in LEDA mit einem normalen Debugger, so wie ich sie kenne, nur mitäußerster Mühe durchführen. Durch die vielen Cast-Operatoren in den Makros muss man beijedem Datum, bei jeder Klasse, von einem Pointer auf void ausgehen, der dann oft über lange,vielfach geschachtelte Castings erst seinen wahren Inhalt verrät. Da ist die Instrumentierung desCodes mit printf-Funktionen sehr viel einfacher.

Trotz alledem ist LEDA ein sehr gutes Produkt, vor allem, weil es eine so große Menge an mäch-tigen Algorithmen vereint.

2.1.1 Step 1

ProjektLEDA ORIG

C++

ProjektLEDA MOD1

C++

- Step 1 -

Jetzt aber zu den Schritten. Die erste Transformationsstufe betraf in erster Linie Namensräume,verschiedene Makros, die Speicherverwaltung, das Multithreading und das Verschmelzen vonDateien.

Die Makros wurden als erstes bearbeitet, da sie in den anderen Bereichen überall verwendetwurden. Das war Handarbeit in maximaler Form, da die Reihenfolge immer beachtet werdenmusste. Nur wenn ein Makro mit anderen Makros nicht verschachtelt war, durfte es überall durchseinen Text ersetzt werden. Hierzu wurden reguläre Ausdrücke definiert, mit denen eine Volltext-suche mit Ersetzung stattfand. Ein ständiges Übersetzen des Quellcodes und Ausführen einigerkleiner Programme gewährleistete, keinen Fehler dadurch produziert zu haben. Die Codemengewurde dadurch natürlich sehr viel größer, da die Makros im allgemeinen auf eine größere Text-menge abbildeten.

Es gab auch vielfach Dateien, die nur aus einem Makro wie #define NumberType XY und einemInclude-Befehl wie #include "algorithms.c" bestanden. Durch dieses Konzept wurden, ähnlichwie mit C++-Templates, spezielle Instanzen von Algorithmen realisiert. Diese Makros und vorallem diese Dateien (algorithms.c) aufzulösen, vervielfachte die Codemenge beträchtlich.

Vorerst wurden allerdings nicht alle Makros aufgelöst, sondern nur die einfachen, wie zum Beispieldie von Konstanten. Die Möglichkeit, mit CodeWarrior nur den Präprozessor auszuführen, wurde

Die Entwicklung der GGL

11

oft zur Kontrolle einzelner Segmente genutzt, aber konnte leider so nicht direkt für eine Weiter-verwendung der Ergebnisse genutzt werden, da auch die C++-Standardbibliothek aus unzähligenHeaderfiles besteht, die vom Präprozessor Gebrauch machen und bei so einem Schritt ebenfallsmit in Bezug genommen werden.

Dann war die LEDAs eigene Speicherverwaltung an der Reihe. In ihr ist übrigens auch der Kopier-schutz verankert. Sie hängt ebenfalls stark vom Präprozessor ab, hat dann aber im Kern ein paarrelativ einfache Routinen.

Daraufhin kamen die alles durchsetzenden Handler für das Multithreading an die Reihe. Siebasierten auf der Header-Datei "mutex.h" und waren auf den ersten Blick recht kompliziert.Nachdem aber einige Makros dazu eliminiert wurden, konnte man die Zusammenhänge leichtverstehen und sie alle konsequent auslöschen.

Danach folgte wiederum eine Menge Handarbeit mit den C++-Namensräumen. Dazu wurde eineinfaches Prefixing verwendet, d. h., man nimmt sich den Namen des Namensraumes, der implizitoder explizit gegeben ist, und setzt ihn vor alles in diesem Namensraum definierte. Danach kannman die Deklaration des Namensraumes löschen, ohne Namenskollisionen erwarten zu müssen.So einfach das Grundprinzip auch ist, so muss man hierbei trotzdem sehr vorsichtig vorgehen. Dasheißt auch, dass man stetig kompilieren und testen muss.

Der Abarbeitung großer Namensräume - damit ist die Menge der enthaltenen Deklarationengemeint - kann man am besten mit Listen entgegenkommen, die mithilfe von regulären Aus-drücken und kleinen Programmen zur Automatisierung automatisch abgearbeitet werden. Aber,wie eben schon gesagt, muss man vor so einer Operation immer das gesamte Projekt sichern. Mitsolchen über Nacht aktivierten Automatisierungen habe ich zumindest einmal eine böse Über-raschung erlebt. Fehler lassen sich hier, sofern eine falsche Textersetzung stattgefunden hat,aufgrund der Codemenge nur durch die Benutzung der Vorgängerversion beheben. Um zu sehen,ob alles mit rechten Dingen zugegangen ist, darf nicht nur kompiliert, sondern muss immer auchgetestet werden.

2.1.2 Step 2

ProjektLEDA MOD1

C++

ProjektLEDA MOD2

C++

- Step 2 -

Der Code ist nun also bereinigt, aber in einer schlechten Form, man denke nur an die Benennun-gen durch vorherigen Prozess. In diesem Schritt geht es darum, den Code auf die letzte C++-Variante zu bringen, bevor der Schritt nach Java vollzogen wird. Das bedeutet, dass nach erfolg-reichem und gründlichen Regressionstest (s. o.) nun möglichst für alle noch enthaltenen Sprach-features Regeln existieren bzw. denkbar sind, um danach eine einfache Abbildung nach Java zuerreichen. Zum Beispiel müssen die Namen korrigiert werden ohne dabei neue Namenskollisionen

Die Entwicklung der GGL

12

einzuführen - am besten gleich in das Format, das den verwendeten Java Coding Standards (sieheKapitel 2.2) genügt.

Diese Coding Standards beinhalten aber auch, und das kann gleich in diesem Schritt mitbehandeltwerden, Regeln, die die Codestruktur betreffen, die Schachtelungstiefe in Schleifen, homogenewohl definierte Austrittspunkte in Schleifen und Fallunterscheidungen und vieles mehr dieser Art.Am Rande sei bemerkt, dass mit dem Logiscope 5.0 RuleChecker1 mit relativ strikten Regeldefini-tionen der GGL-Code überprüft wurde, um allen Standard-Regeln zu genügen. Tatsächlichkonnte dadurch sogar ein Fehler "gefiltert" werden, der ansonsten sehr schwer auffindbar gewesenwäre (es handelte sich um zwei sonderbar ineinander geschachtelte Schleifen..).

Ein ganz wichtiges Thema ist zum Beispiel auch die Mehrfachvererbung, die es so in Java nichtgibt. Da muss man wirklich umprogrammieren. Schwierig war es allerdings nur für eine Klasse, diedazu auch nur singulär benutzt werden durfte, d. h., bei der es nur erlaubt war, eine einzige Instanzim gesamten Programm bzgl. eines Graph-Objekts zu benutzen: node_list. Da sie aus anderenGründen auch ein Sicherheitsproblem darstellte, wurden sie mit einer normalen Liste(List2_Node) substituiert.

Ein anderes wichtiges Thema ist das Schlüsselwort virtual. Da in C++ bestimmt werden kann,welche Funktionen virtual sind und welche nicht, in Java aber alle Methoden virtual sind, mussman zur Not, sofern man Funktionen nicht zusammenfassen kann, neue Namen entwickeln, umein ungewolltes Überschreiben durch abgeleitete Klassen zu vermeiden.

Auch der direkte Zugriff auf überlagerte Funktionen von Basisklassen ist in Java nicht mehrmöglich. Ein Casting auf die definierende Klasse reicht hier nicht. In C++ hat man über denNamensoperator "classxy:" immer noch Zugriff auf die ursprünglichen Methoden von außen. InJava nur über die abgeleitete Klasse selbst, über das Schlüsselwort super. Hier hilft im Zweifelsfallnur eine "Wrapper"-Methode in der Basisklasse unter neuem Namen.

Dies alles sollte bereits in diesem Schritt Beachtung finden. Umso einfacher wird der letzte undwichtigste Schritt von C++ nach Java.

2.1.3 Step 3

ProjektLEDA MOD2

C++

ProjektGGLJAVA

- Step 3 -

Nun kommt also die Abbildung nach Java. Hierzu müssen auf jeden Fall die Destruktoren ausdem C++-Code eliminiert werden. Überall, wo ein Objekt einer Klasse im Code "vernichtet" wird,müssen dafür die Code-Inhalte des Destruktors eingebaut werden. Meist geht es nur darum,

1 Werkzeug der Firma Telelogic: www.telelogic.de

Die Entwicklung der GGL

13

enthaltene Elemente eines Objekts mitfreizugeben, aber es können natürlich auch andere Operati-onen damit verbunden sein, z. B. das Entfernen eines Eintrags aus einem Wörterbuch.

Am einfachsten setzt man dies in einem ersten Schritt so um, dass der gesamte Code des Destruk-tors in eine Methode verfrachtet wird, und ruft diese Methode überall dort zusätzlich auf, wo dieObjekte wieder freigegeben werden. Das Problem besteht nur darin, zu erkennen, wo überall dieObjekte wieder aufgelöst werden, da dies zum Großteil implizit geschieht. Auch muss beachtetwerden, dass die Destruktoren der Superklasse ebenfalls ausgeführt werden.

Erwähnen möchte ich auf jeden Fall noch die Wertübergabe über Referenzen, da man daraufvielleicht nicht so schnell kommt, obwohl das Prinzip sehr einfach und auch effizient ist.

Die Wertübergabe mittels Referenzen wird in C++ meist über den &-Operator realisiert. Dies wirdmeist gemacht, um über die Argumente einer Funktion auch gleich die Ergebnisse zurückzugeben.Falls es sich bei den Argumenten nicht um eine Containerklasse handelt, dann hat man einProblem mit einer Eins-zu-eins-Übersetzung nach Java - zumindest bis man versteht, wie man esumgehen kann.

Die Lösung ist die Verwendung von Native-Java-Arrays der Größe 1. Hat man also eine C++-Funktion, die ungefähr so aussieht:

int doIt(int& x);

dann definiert man stattdessen:

int doIt(int[] x);

und muss natürlich überall im Code die Anpassungen für die Argumente vornehmen:

int[] newX = new int[1];

newX[0] = x;

doIt(newX);

Später kann man diese Hilfskonstruktion natürlich von vorneherein für x einsetzen.

Dies war nur ein kleiner Ausschnitt aus der Fülle an Feinheiten, die es zu beachten gilt, wenn manso eine Transformation, wie die mit LEDA, vorhat zu bestreiten. Beispielsweise ist das gesamteOperator-Overloading ein großes Problem für sich. Viele Probleme konnten nur durch massivesInstrumentieren des Codes sicher gelöst werden, um alle impliziten Vorgänge zu erfassen undabzufangen. Auch nicht zu unterschätzen sind die C++-Templates, für die in dieser Arbeit aller-dings ein Konzept für Java gefunden wurde. Ohne dieses Konzept wäre es nur möglich gewesen,alle Templates auf die Art und Weise zu eliminieren, dass sie von Hand für eine eingeschränkteAuswahl instantiiert und dann als konkrete Datentypen und Algorithmen in die GGL mitauf-genommen worden wären. Kurzum, man hätte das erste "G" von GGL streichen müssen.

Die Entwicklung der GGL

14

2.1.4 Zu Hilfsbibliotheken wie der JGL

Noch etwas zu dem Einsatz von Hilfsbibliotheken mit Standarddatentypen, wie z. B. der JGL1 fürTransformationsaufgabe wie diese. Er ist, grob gesagt, nicht zu empfehlen! Dies liegt vor allemdaran, dass die verfügbaren Methoden andere sind als die der Standarddatentypen in LEDA odereiner vergleichbaren Bibliothek - jeder kocht meist sein eigenes Süppchen. Man erhofft sich ersteine Erleichterung, weil man denkt, die Standarddatentypen nicht transformieren zu müssen undfängt dann an, überall die alten Funktionsaufrufe mit denen der neuen Basisklassen zu substi-tuieren. Dann erkennt man aber, spätestens ab einem gewissen Punkt, dass der Aufwand vielgrößer ist als die ursprüngliche Basisklasse zu transformieren und dass ein Problem auch darinbesteht, dass eine zusätzliche Fehlerquelle eingebaut wird.

Die hieraus abgeleitete Regel heißt also einfach und grob: "Transformiere das ganze System undnimm vorerst nur auf die Bibliotheken Rücksicht, die Du einbinden musst."

Als Quelle der Inspiration für generische Programmierung war die JGL leider auch nicht sehrgeeignet. So basiert das, was sie unter "generisch" versteht, nur auf dem Standardkonzept objekt-orientierter Programmierung mithilfe des Einsatzes eines ganzen Systems aus Interfaces undabstrakten Klassen und der ständigen Verwendung von Fallunterscheidungen der Art "if xyinstanceof then".

Hierzu möchte ich anmerken, dass so ein System nur elegant ist für den 1-dimensionalen gene-rischen Betrieb. Das heißt beispielsweise, dass echte Deep-Copies damit nicht implizit möglichwerden, sondern nur sogenannte Shallow-Copies, bei denen lediglich Objektreferenzwerte kopiertwerden. Will man mehr, so muss für jeden speziellen Datentyp und jede spezielle Operation eineKlasse kreiert werden, die ein spezielles Interface oder eine abstrakte Klasse erbt. Dies kann zueiner explodierenden Menge ausarten.

Gleiche Erfahrungen habe ich auch mit den Argumentlisten von Methoden gemacht, die unsäglichlang werden können, um das Thema gleich abzuschließen.

Zuletzt noch ein Blick auf die Antwort der JGL auf die Umsetzung der "generischen" numerischenMethode für Addition. Bei der Inspektion der Klasse stößt man auf einen Code, der ungefähr soaussieht:

static Number plus( Number n1, Number n2, Class mode ) { // normal subclasses if ( mode.equals( Integer.class ) )

return new Integer( n1.intValue() + n2.intValue() );if ( mode.equals( Short.class ) )

return new Short( (short)( n1.shortValue() + n2.shortValue() ) );

// compare as BigIntegersif ( mode.equals( BigInteger.class ) )

return asBigInteger( n1 ).add( asBigInteger( n2 ) );..

1 Java Generic Library, siehe www.objectspace.com

Die Entwicklung der GGL

15

.. // compare as BigDecimals if ( mode.equals( BigDecimal.class ) ) return asBigDecimal( n1 ).add( asBigDecimal( n2 ) );

// don't know how to deal with mode throw new IllegalArgumentException ("unknown subclass of java.lang.Number: " + mode.getClass());

}

Man erkennt, dass die Implementierung dieser "generischen" Plus-Operation zwar sehr weit geht,doch scheint sie durch die vielen Fallunterscheidungen nicht sehr effizient zu sein, zumindest fürdie zuletzt bearbeiteten Number-Klassen. Und das letzte Statement ist der Beweis, dass es, strengbetrachtet, keine oder nur eine sehr eingeschränkte generische Operation ist.

2.2 Optimierung und ErgänzungBei der Entwicklung der GGL wurde nach vollendeter Transformation von C++ nach Java sehrviel Arbeit in eine weitere Optimierung und Ergänzung gesteckt. Es wurde der gesamte Codeeinem Komplettumbau unterzogen, um ihn in jeder Hinsicht besser zu machen.

Wie bei einem Pattern-Matching wurde gezielt nach obskuren Konstruktionen Ausschau gehalten,um diese dann inhaltlich zu erfassen, neu umzusetzen und dann auf alle ähnlichen Muster anzu-wenden. Die Homepage von Martin Fowler [WMA02], einer der Väter des Refactoring1, dientedazu in manchen Fällen als Inspiration.

Für die Laufzeitoptimierung wurde anfangs ein Java-Profiler2 eingesetzt, um kritische Stellen,damit sind oft durchlaufene Codezeilen gemeint, zu erkennen und zu verfeinern. Letztendlichwurde aber auf den weiteren Einsatz verzichtet und nach allen Regeln der Kunst die gesamteBibliothek gleichermaßen optimiert. Der Vollständigkeit halber wurde sie zusätzlich um vieleweitere Operationen und einige Klassen ergänzt. Diese Phase sollte die GGL zu einem Qualitäts-produkt machen.

Die Optimierungen und Ergänzungen bezogen sich auf folgende Aspekte:

- Programmierstil

- universelle Ausrichtung

- Performance

- leichte Verwendbarkeit

- homogenisierte Methoden

1 Fachbegriff für das kontrollierte Ändern des Designs bestehender Software.2 Enthalten in Metrowerks CodeWarrior Professional 4

Die Entwicklung der GGL

16

- homogene Schnittstellen

- möglichst flache Klassenhierarchie bei konstanter Funktionalität

- Vermeidung von Redundanzen => keine "Wrapper"-Methoden

- zusätzliche Funktionalität (z. B. Methoden für die generische Ein-/Ausgabe)

- zusätzliche Dokumentation

- ein Framework für Regressionstest und Benchmark

- zusätzliche generische Datentypen (z. B. ggl.basic.NTuple)

- ein- und ausschaltbare Überprüfungen

- Einhaltung strikter Java Coding Conventions

2.2.1 Java Coding Conventions

In Betracht gezogen und berücksichtigt wurden viele Quellen zu Java Coding Conventions, wobeihier eine repräsentativ für alle genannt sein soll: "The AmbySoft Inc. Coding Standards for Java"[Amb97].

Die von mir begutachteten Konventionen unterschieden sich meist nur in kleinen Details, die manmeist als "Geschmackssache" betrachten konnte. Und natürlich war der Quellcode des JDK in derVersion 1.1.8 ein wichtiges Hilfsmittel, um daraus die Menge eigener Regeln für den verwendetenProgrammierstil abzuleiten. Welche Vorlage kann besser sein als Code der Java-Entwickler selbst.

Die am meisten benutzten Regeln waren natürlich die zur Benennung von Packages, Klassen undMethoden, da Namen im Code öfter vorkommen als bestimmte Konstruktionen:

- Klassennamen werden groß geschrieben, wie z. B. Graph

- Methoden werden klein geschrieben, wie z. B. copy

- Globale Konstanten werden durchgängig groß geschrieben, wie z. B. AFTER

- Underscores ("–") werden vermieden, indem stattdessen durch Groß-/Kleinschreibung dieTeilworte differenziert werden, wie z. B. getIndex().

Manche Regeln wurden durch andere wiederum eingeschränkt. Zum Beispiel die letztgenanntemit den Underscores. Für globale Konstanten eignet sie sich nämlich ebenso wenig wie für dieDifferenzierung der statischen Spezialisierung einer generischen Klasse. Das heißt, dass in diesenFällen Underscores nicht nur erlaubt, sondern auch vorgeschrieben waren. Als Beispiel sei diestatisch spezialisierte dynamische Listen-Klasse für Integer-Werte gezeigt: List2_Int - dazu aberspäter mehr in Kapitel 3.4.

Benutzerhandbuch

17

3 Benutzerhandbuch

Dieses Kapitel ist der Nutzung der Generic Graph Library for Java (kurz GGL) gewidmet. Jeder,der vorhat mit der GGL zu arbeiten, sollte dieses Kapitel gründlich lesen, um auf einfache Art undWeise und in relativ kurzer Zeit ein tiefes Verständnis für die GGL zu entwickeln und produktivmir ihr zu arbeiten. Der Leser wird hierbei Schritt für Schritt und mithilfe einiger Beispiele an dieGGL herangeführt. Um mehr Beispiele zu bekommen, sollte ein Blick auf den Quellcode derTestklassen ggl.test.XY geworfen werden. Sie werden im API-Guide beschrieben und könnendann gezielt ausgewählt werden. Sie sind mit vielen Kommentaren erläutert.

Der Aufbau dieses „Buches“

Im ersten Kapitel 3.1 „Einführung“ wird beschrieben, was die GGL ist, es wird auf Voraussetzun-gen zur Nutzung der GGL eingegangen und gezeigt, wie man sie installiert, selbst übersetzt undtestet. Außerdem zeigt sie, wie Beispiele aus dieser Dokumentation ausgeführt werden und wowelche Dokumentation zu finden ist.

Das Kapitel 3.2 „Umfang der GGL“ enthält eine kleine Statistik über die Klassen der GGL,beschreibt die Struktur der GGL, d. h., welche Packages in ihr enthalten sind und gibt eine Kurz-beschreibung über die Inhalte.

Das Kapitel 3.3 „Allgemeines zur Implementierung“ beschreibt verschiedene Charakteristika derImplementierung zu den Themen "Kompatibilität mit anderer Software", Programmierstil, Ein-/Ausgabe, Random-Funktionen und Debug-Implantate.

Das generische Konzept der GGL wird im Kapitel 3.4 „Generisches Programmieren mit Type“beschrieben. Dieses ist wichtig, um die Grundzusammenhänge der GGL zu verstehen. Hierzugehört natürlich, wie man selbst generische Klassen erzeugt und vorhandene für bestimmteDatentypen spezialisiert.

Das Kapitel 3.5 „Datentypen und Algorithmen“ sagt ein paar zusätzliche Worte hinsichtlich desAPI-Guides zu den Basisdatentypen, den Graphalgorithmen und der Klasse Graph.

Daraufhin geht das Kapitel 3.6 „Iteratoren“ auf die Implementierung der Iteratoren ein, mitdenen Elemente der GGL-Containerklassen in definierter Reihenfolge durchlaufen werdenkönnen.

Das Kapitel 3.8 „Das Testsystem“ gibt ein paar Hinweise zu dem integrierten Testsystem und dasdarauf folgende Kapitel 3.9 „Performance“ gibt eine komprimierte Auskunft zu dem Laufzeit-verhalten der GGL.

Benutzerhandbuch

18

3.1 EinführungDie Generic Graph Library for Java (GGL) ist eine auf Graphen und generische Programmie-rung spezialisierte Klassenbibliothek für Java. Sie enthält eine große Menge universell einsetzbarerDatenstrukturen und Algorithmen für Graphen und eignet sich daher besonders für Graphen- undNetzwerkprobleme. Neben diesem Schwerpunkt zeichnet sie sich besonders dadurch aus, dass mitihr ein neues Konzept generischer Programmierung in Java bereitgestellt wird. Mit diesem Konzeptverbunden, enthält sie viele der grundlegenden Datentypen wie Felder, Listen, Schlangen undWörterbücher, die dadurch ein Höchstmaß an universeller Einsetzbarkeit erhalten. Sie ist daher inallen Softwareprojekten einsetzbar und dürfte auch für Entwickler interessant sein, die sich aus-schließlich für eine effiziente Form der generischen Programmierung mit Java interessieren, ohnevon den Graph-Datenstrukturen und Algorithmen Gebrauch machen zu wollen.

Die GGL ist objektorientiert und erweitert die Standardklassenbibliothek des Java DevelopmentKits (JDK), die gleichsam Grundlage für die GGL ist. Sie wurde auf Kompatibilität mit JDK 1.1bis 1.4.1 getestet und ist damit auf nahezu jeder konventionellen Computerplattform einsatzfähig.

Als Vorlage diente die renommierte C++ Klassenbibliothek LEDA1 vom Max Planck Institut, dieunter anderem in den Gebieten Telekommunikation, GIS, VLSI Design, Verkehrsplanung,Bioinformatik und Medizin für Softwareprojekte Anwendung findet.

Die GGL ist einfach zu benutzen und leicht zu durchschauen, wodurch Fehler in eigenenProgrammen leicht zu lokalisieren sind. Sie ist effizient, was Laufzeitverhalten und Speicherbedarfangeht, aber auch, was die Menge an verfügbaren Operationen betrifft. Damit wird eigener Codekurz und kompakt, ist somit einfach zu lesen und zu begreifen. Die GGL ist einfach zu erweiternund alle in ihr enthaltenen Klassen und Methoden sind gut dokumentiert.

3.1.1 Dokumentation

Die Dokumentation ist ein wichtiger Bestandteil der GGL. Sie gliedert sich in diese schriftlicheAusarbeitung der Diplomarbeit und den Application Programming Interface Guide (kurz API-Guide) der GGL. Wie eingangs erwähnt, beinhaltet die vorliegende Dokumentation vor allem dieEntwicklung der GGL (Kapitel 2) und das Kapitel „Benutzerhandbuch“ in dem wir uns geradebefinden und das den wichtigsten Einstieg in die GGL darstellt. Es gibt aber auch noch andereQuellen, die für die GGL von Bedeutung sind und hier ihre Erwähnung finden sollen.

Benutzerhandbuch

Das Benutzerhandbuch führt den Leser systematisch durch die GGL und zeigt ihm, was sie ist, wassie kann und wie sie zu benutzen ist. Eine Kurzbeschreibung der Struktur und Inhalte findet sichin Kapitel 3.2.2 "Packages der GGL".

1 Information und Bezug unter http://www.algorithmic-solutions.de

Benutzerhandbuch

19

API-Guide

Für die Dokumentation der vollständigen Schnittstelle der Bibliothek und einer detailliertenDokumentation aller Klassen und der in ihnen enthaltenen Methoden ist der API-Guide vor-handen. Die Dokumentation ist nach Packages gegliedert und besteht für jede Klasse aus einemallgemeinen Teil über die Klasse an sich und einem weiteren, in dem die dem Entwickler zurVerfügung stehenden Variablen und Operationen einzeln aufgeführt und beschrieben sind. Derallgemeine Teil, der die Definition des die Klasse repräsentierenden Datentyps enthält, stellt oftauch Informationen zur Implementierung und zum Laufzeitverhalten verschiedener Operationenzur Verfügung. In ihm sind auch die Informationen enthalten, die für eine Menge von Operatio-nen gleichermaßen zutreffen, um Redundanz in der Dokumentation möglichst niedrig zu halten.Für den Leser heißt das, dass er zur vollständigen Dokumentation einer Operation oft auch denallgemeinen Teil hinzuzuziehen hat, um vollständigen Aufschluss zu erhalten.

Der API-Guide ist die wichtigste Dokumentation zur GGL neben diesem Benutzerhandbuch. Dermit der GGL erfahrene Programmierer wird das Benutzerhandbuch sicherlich bald zur Seite legenund nur noch in seltenen Fällen darauf zurückgreifen, der API-Guide wird jedoch sein ständigerBegleiter sein.

Der API-Guide ist in den Quellcode der GGL integriert und somit bei einer Inspektion des Quell-codes ständig präsent. Er wurde mit javadoc1 nach HTML transformiert, mit BBEdit2 undDreamweaver3 nachbearbeitet und dann mit Adobe Acrobat4 nach PDF transformiert und derDistribution in diesen beiden Formaten beigefügt. Der Umfang von über 1000 Seiten (PDF) gibteine Vorstellung über die Komplexität der Dokumentation, aber auch der GGL.

Java-Dokumentation

Da die GGL eine Klassenbibliothek für Java ist, sollte der Leser dieses Buches über ausreichendesWissen auf dem Gebiet „Programmieren mit Java“ verfügen. Falls dies noch nicht der Fall ist, seiihm das Buch „The Java Programming Language, Second Edition“ von Ken Arnold und JamesGosling [AG99] empfohlen. Zusätzlich benötigt er die API-Dokumentation zur Java-Standard-bibliothek (JDK 1.1 oder höher) [JDK98], die jeder Java-Entwicklungsumgebung beiliegt, da aufeine Vielzahl der in ihr enthaltenen Klassen seitens der GGL Bezug genommen wird.

Literatur zu den Graph-Algorithmen

Ein Schwerpunkt der GGL liegt auf den Graph-Algorithmen. Wem die Dokumentation seitensdes API-Guides zu knapp ist, der kann auf die Primärliteratur (siehe Kapitel 5 "Literatur"), füreine schnelle leicht verständliche Anleitung auf den LEDA Guide [LDG03] und zum anderen aufverschiedene Bücher zu LEDA zurückgreifen, die auf der Website von Algorithmic Solutionsaufgelistet sind5. Die gesamte Dokumentation aus dem LEDA Manual [LUM99] und dem LEDA 1 Teil des Java-Frameworks2 Texteditor der Firma Bare Bones Software; siehe http://www.barebones.com3 Werkzeug der Firma Macromedia zum Erstellen von Websites; siehe

http://www.macromedia.com/software/dreamweaver/4 Siehe http://www.adobe.com/acrobat/5 Siehe http://www.algorithmic-solutions.de/ledaDokuMitte.htm

Benutzerhandbuch

20

Guide ist in den GGL API Guide [Win03] miteingeflossen und wurde zusätzlich ergänzt. Hierinfinden sich bei der Beschreibung der Algorithmen auch die im Literaturindex aufgeführtenReferenzen zu der verwendeten Primärliteratur.

3.1.2 Voraussetzungen

Um die GGL nutzen zu können, benötigt man Java-Programmiererfahrung und damit auchWissen über die Konzepte objektorientierter Programmierung. Um das generische Konzept derGGL zu verstehen sind Erfahrungen mit C++ Templates hilfreich, aber nicht Bedingung. Aufjeden Fall bedarf es einer Java-Entwicklungsumgebung, die kostenfrei von Sun Microsystems1 fürdie meisten der konventionellen Betriebssysteme erworben werden kann, um Java-Programme zuübersetzen und auszuführen. Neben den Sun-Standardwerkzeugen kann eine zusätzliche integrierteEntwicklungsumgebung (IDE) von Drittherstellern von Vorteil sein, die neben speziellen Editorenund Browsern meist auch ein sehr viel besseres Projektmanagement bietet.

3.1.3 Die Distribution

Mit der Distribution ist das Gesamtpaket gemeint, mit dem die GGL zur Verfügung gestellt wird.Die Distribution der GGL in der Version 1.0 hat folgende Struktur und Inhalt:

\GGL100 Hauptverzeichnis

\readme.txt Kurze Orientierungshilfe

\doc Dokumentation\index.html Ausgangspunkt für die gesamte Dokumentation\diplomarbeit.pdf Das vorliegende Dokument in PDF\api_guide.pdf Der API-Guide in PDF\api\* Der API-Guide in HTML-Format\images\* Bildressourcen der HTML-Dokumentation

\lib\ggl100.jar Übersetzte Bibliothek im JAR-Format

\src\ggl Quellcode der Bibliothek\basic\* Quellcode der Package ggl.basic\graph\* Quellcode der Package ggl.graph\graph\Graph.java Quellcode der Klasse Graph.. etc.

\examples\* Code-Beispiele dieser Dokumentation

\test

\GglRegTest_stdout.txt Komplette Ausgabe des Regressionstest(Szenarien 1 - 10)

1 Zu beziehen über www.sun.com

Benutzerhandbuch

21

\GglRegTest01_stdout.txt Ausgabe des Regressionstest für Szenario 1\GglRefTestFiles\* Referenztestdateien zum Testen der GGL

auf korrekte Funktionalität

Für das Lesen der Dokumentation im HTML-Format eignet sich jeder gängige Webbrowser wiez. B. Netscape1 oder Internet Explorer2. Für das Format PDF von Adobe empfiehlt sich der AdobeAcrobat Reader3. Diese Zweigleisigkeit in der Distribution der Dokumentation des API-Guideswurde gewählt, weil beide Formate ihre Vor- und Nachteile haben. So lässt sich beispielsweise eineVolltextsuche über die gesamte API-Dokumentation mit dem Acrobat Reader leichter bewerk-stelligen als mit einem Webbrowser. Auch sind die Darstellung und die Navigierbarkeit sehr unter-schiedlich. Im allgemeinen ist HTML hier die erste Wahl, aber natürlich sei es dem Leser selbstüberlassen, für welche Variante er sich in seiner jeweiligen Situation entscheidet.

Zum Quellcode Layout sei anzumerken, dass es den strikten Java-Konventionen entspricht,wonach jede Datei nach der in ihr enthaltenen (Haupt-) Klasse benannt ist (mit zusätzlichem.java-Suffix). Jede Datei ist in einem Ordner enthalten, der dem Namen der übergeordnetenPackage der enthaltenen Klasse gleich kommt.

Der Ordner \examples enthält alle Beispiele aus dieser Dokumentation, durchnummeriert nachVorkommen.

Der Ordner \test enthält die Referenztestdateien, die zum Testen der GGL auf korrekte Funktio-nalität dienen. Er enthält auch die Ausgabe, die über stdout ausgegeben wird, wenn derRegressionstest abläuft. Sie ist beigefügt, um sich leicht ein Bild von den Abläufen, die in demRegressionstest passieren, machen zu können. Abgesehen von den unterschiedlichen Zeilenendun-gen für Textdateien unterschiedlicher Betriebssysteme (DOS, Macintosh, Unix) wird die Ausgabebei erfolgreich absolviertem Regressionstest immer die gleiche sein.

3.1.4 Installation

Zur Installation der GGL sollte der komplette Inhalt der Distribution in ein lokales bzw. ständigverfügbares Verzeichnis kopiert werden, damit alle Bestandteile, vor allem die Dokumentation,jederzeit und an klar definierter Stelle erreichbar sind. Nichtsdestotrotz wird für die reine Nutzungnur die bereits übersetzte Bibliothek benötigt. Man kann also, vor allem wenn Platzmangel besteht,die anderen Bestandteile auf einem externen Datenträger belassen und nur bei Bedarf daraufzurückgreifen.

Damit die Java-Entwicklungsumgebung von der Existenz der GGL erfährt und damit alle Klassenin eigenen Projekten verwendet werden können, muss die Umgebungsvariable CLASSPATH sogesetzt bzw. ergänzt werden, dass sie auf das Verzeichnis zeigt, in dem sich die lokale Kopie derBibliothek befindet. Wie dies im Detail geschieht, entnimmt man bitte der Dokumentation, die

1 Download unter www.netscape.com2 Download unter www.microsoft.com3 Download unter www.adobe.com

Benutzerhandbuch

22

mit der eigenen Java-Entwicklungsumgebung mitgeliefert wurde, da dieser Vorgang abhängig vomverwendeten Betriebssystem ist.

Bei Problemen bietet sich noch ein kleiner Umweg an, indem man die Bibliotheksdateiggl100.jar einfach in ein bestehendes Bibliotheksverzeichnis kopiert, welches entweder über dieVariable CLASSPATH bereits adressiert ist oder implizit vom Betriebssystem als gültiger Aufbewah-rungsort für Java-Bibliotheksdateien betrachtet wird.

3.1.5 Übersetzen der Bibliothek

Um die Bibliothek selbst ins Binärformat zu übersetzen, kann man das auf allen Java-Entwick-lungsplattformen verfügbare Kommandozeilenwerkzeug javac benutzen. Dazu verwendet man einTerminal, navigiert in den Ordner src/ggl/, legt dort einen Ordner classFiles an und gibtfolgendes Kommando ein:

javac basic/*.java graph/*.java graphAlg/*.java graphIO/*.java type/*.javautil/*.java inst/*.java test/*.java –d classFiles

Hierdurch wird der gesamte Quellcode der GGL kompiliert und die resultierenden Binärdateien(auch Klassendateien genannt) strukturiert in den Ordner classFiles abgelegt. Um die Klassen-dateien zu einem bibliothekskonformen jar-Archiv zu kombinieren, existiert das Kommando jar.

Falls nur einzelne Dateien übersetzt werden sollen, so ist zu beachten, dass sie meist von anderenKlassen der GGL abhängig sind, die sich nicht in derselben Datei befinden (jede Datei enthält nureine Hauptklasse, kann aber noch Unterklassen beinhalten). In diesem Fall muss dem Compilermitgeteilt werden, wo sich die Quelldateien mit den referenzierten Klassen oder die bereits kompi-lierten Klassendateien befinden, da er sie für den Übersetzungsvorgang benötigt. Geschieht diesnicht, so bekommt man Fehlermeldungen der Art „can not resolve symbol xxx“ – und zwarnicht nur eine, sondern meist so viele, dass man davon fast erschlagen wird.

Bezogen auf obigen Kompiliervorgang nun das Beispiel, wie eine einzelne Datei kompiliert werdenkann. Es wird davon ausgegangen, dass die Bibliothek bereits, wie oben gezeigt, übersetzt wurdeund somit der Ordner classFiles mit den Klassendateien gefüllt ist. Zuerst navigiere man mitdem Terminal wieder in den Ordner src/ggl/ und gibt dann folgendes Kommando ein:

javac graph/Edge.java –classpath classFiles –d classFiles

Hierdurch wird die Quelldatei Edge.java erfolgreich kompiliert und das Ergebnis wiederum inden Ordner classFiles geschrieben. Zu beachten ist, dass die Parameter zum Bestimmen desBenutzerklassenpfades (-classpath) und des Zielverzeichnisses (-d) auf manchen Plattformenleicht differieren. Die jeweilige Man-Page (man javac) gibt aber schnell Aufschluss über diekorrekte Schreibweise.

Am komfortabelsten geht es natürlich mit einer auf Java-Entwicklung ausgerichteten IDE(Integrated Development Environment). Ist erst einmal das Projekt mit all den Quelldateienangelegt, reicht für das Übersetzen einer Datei oder der gesamten Bibliothek meist nur ein Knopf-druck und das Schreiben langer Kommandos entfällt. Zudem lassen sich einzelne Compiler-optionen ebenso einfach aktivieren wie deaktivieren.

Benutzerhandbuch

23

3.1.6 Testen der Bibliothek

Zum einfachen Testen, ob die Bibliothek in die eigene Entwicklungsumgebung integriert ist,empfiehlt es sich, ein kleines Programm, das die GGL benutzt, zu kompilieren und auszuführen(siehe Abschnitt 3.1.8 „Beispiele ausführen“). Zum Testen der Bibliothek auf korrekte Funktio-nalität und gute Performance vor allem bzgl. der eigenen Java-Plattform bietet sich das integrierteTestsystem an, das im Kapitel 3.8 kurz und im API-Guide [Win03] genauer beschrieben wird.Die GGL ist zu einem Großteil selbsttestend.

3.1.7 Debuggen mit der Bibliothek

Debuggen gehört zum täglichen Geschäft eines Programmierers. In manchen Fällen kann es sehrlehrreich sein, hinter die Schnittstelle der Bibliothek, wie sie durch den API-Guide dokumentiertist, zu schauen, um zu sehen, was tatsächlich hinter den Kulissen passiert. In diesem Fall empfiehltes sich, die Bibliothek dem eigenen Projekt im Quellformat hinzuzufügen und die Bibliothek imBinärformat aus der Java-Umgebung wieder zu entfernen. Dies kann entweder durch Ändern derCLASSPATH-Variable oder Verschieben der Bibliotheksdatei ggl100.jar in einen temporärenOrdner geschehen.

Das Resultat ist, dass bis in die kleinsten Winkel der GGL nachvollzogen werden kann, wie dieOperationen ausgeführt werden. Wem das immer noch nicht genug ist, der kann selbiges auch mitder Java-Standardbibliothek tun, sofern er die Quelldateien dazu besitzt (im allgemeinen auch überSun Microsystems1 zu beziehen).

Als Standarddebugger ist das Kommandozeilenwerkzeug jdb verfügbar. Die zugehörige Man-Pagegibt Aufschluss darüber, wie es zu benutzen ist. Voraussetzung für die Debug-Fähigkeit ist ein zusetzendes Compilerflag (meist –g) für den Übersetzungsvorgangs seitens javac, damit die Debug-Informationen generiert und mit in die Binärdateien integriert werden.

Bevor man so weit geht, sollte man sich den Quellcode natürlich erst einmal anschauen. Auch umdas generische Konzept der GGL zu verstehen, sei der Leser ermutigt, den Quellcode zu inspi-zieren. Ebenso sind manche Algorithmen im Quellcode leichter verständlich als die zu ihnengehörige Dokumentation und für eigene Entwicklungen lässt sich sicher das ein oder andere zurWiederverwendung einfach herauskopieren.

3.1.8 Beispiele ausführen

Um Beispiele auszuführen, wie sie zu Lehrzwecken häufig in dieser Dokumentation vorkommen,wechselt man in das Verzeichnis \GGL100\examples, das alle Beispiele enthält und gibt den Befehl

javac xxx.java

1 URL: http:\\www.sun.com

Benutzerhandbuch

24

ein, um das Beispiel xxx zu übersetzen (der Name eines jeden Beispiels ist jeweils am Anfang auf-geführt). Sobald die Übersetzung abgeschlossen ist, gibt man

java xxx

ein, um es auszuführen. Voraussetzung ist die korrekte Installation der GGL wie in Kapitel 3.1.4beschrieben. Natürlich können die Beispiele auch mittels Copy/Paste und einem Texteditor selbstin Dateiform gebracht werden, falls die minimale Installation gewählt wurde und der vollständigeOrdner \GGL100\examples nicht verfügbar ist. Die Benennung der Dateien sollte hierbei ent-sprechend der Hauptklasse erfolgen, um den strengen Java-Konventionen zu genügen. Für dieKlasse Example01 beispielsweise Example01.java. Die ersten Beispiele finden sich im nächstenKapitel 3.4.2 „In der Anwendung“.

3.2 Umfang der GGLDieses Kapitel gibt einen ersten inhaltlichen und strukturellen Überblick über den Umfang derGGL. Die wahre Größe lässt sich sicherlich erst vollständig erkennen, wenn man mit ihr arbeitetund alle Facetten der GGL kennengelernt hat. Die API-Dokumentation [Win03] gibt beispiels-weise Aufschluss über alle Klassen und Methoden, die in der GGL enthalten sind. Durch dasverwendete generische Konzept lassen sich zusätzlich, durch wenige Handgriffe, beliebig komplexeStrukturen erzeugen, für die eine große Anzahl der generischen Algorithmen gültig bleibt.

Die GGL enthält alle Graph-Algorithmen und -datenstrukturen der LEDA-Bibliothek in derVersion 3.8a. Zusätzlich enthält sie alle Grunddatentypen und -algorithmen von LEDA undviele Zusätze. Dies sind im wesentlichen Ergänzungen der Datentypklassen, ein Testsystemund ein Framework zur generischen Programmierung, das bereits eine umfangreiche Unter-stützung der Basisdatentypen des JDK und vieler Basis- und generischer Datentypen der GGLselbst enthält.

Die Anzahl der Datentypen und auch die der Klassen allgemein, sind allerdings sehr viel wenigerim Vergleich zu denen in LEDA enthaltenen, da konsequent darauf geachtet wurde, die Klassen-hierarchie klein zu halten und Redundanzen zu vermeiden. Auch wurden ähnliche Klassen oft zueiner Klasse kombiniert, um die Schnittstelle für den Benutzer möglichst einfach zu gestalten. Fürdie GGL ist dies ebenfalls von Vorteil, da viele Methoden dadurch nicht mehr in zwei- und drei-facher Ausführung vorhanden sein müssen bzw. unnötige Umkopieroperationen von einerContainerklasse in eine ähnliche vermieden werden, nur um eine bestimmte Methode nutzen zukönnen.

Die Funktionalität, die Anwendbarkeit und die Geschwindigkeit wurde mit der Reduzierung derDatentypklassen also zusätzlich erhöht, wobei der Speicherbedarf abnahm und dem Benutzer dasLeben dadurch leichter gemacht wurde. Dies sollte für die nun gleich folgende kleine Statistik mitin Betracht gezogen werden. Die Struktur der GGL in Form von Packages und die Klassifizierungihrer Inhalte wird im darauf folgenden Abschnitt erläutert. Für eine verfeinerte Darstellung stehtder Anhang A "Klassen der GGL" bereit.

Hier nun ein paar Zahlen, die den Umfang und die Proportionen der GGL verdeutlichen:

Benutzerhandbuch

25

Allgemein:

- Größe Quellcode: 2092.820 Byte- Größe der kompilierten Bibliothek: 662.664 Byte- Anzahl Packages: 8- Anzahl Dateien: 204

Ordner/Package:

- basic: 24 Dateien: 309.511 Byte- graph: 21 Dateien: 365.718 Byte- graphAlg: 12 Dateien: 542.614 Byte- graphIO: 6 Dateien: 56.486 Byte- util: 5 Dateien: 86.517 Byte- type: 53 Dateien: 361.691 Byte- inst: 55 Dateien: 157.822 Byte- test: 28 Dateien: 212.461 Byte

Regressionstest:

- Referenztestdateien (GglRefTestFiles): 9888 / 33.865 KB- Ausgabe über stdout: 7 MB- pro Szenario: ca. 700 KB

API-Dokumentation [Win03]:

- HTML: 3 MB in 402 Dateien- PDF: 11,4 MB / 1023 Seiten

3.2.1 Klassenstatistik

Die Klassen der GGL sind unterschiedlichster Natur, weshalb die Angabe der Klassenanzahl alleinekeine sehr aussagefähige Größe ist. Einige Namen der nun differenzierten Klassen-Klassen werdendem Leser unter Umständen noch nicht einleuchten. Nach der Lektüre des Kapitels 3.4 wird er siehoffentlich verstehen. Aufgelistet sind nur Klassen, die von außen seitens des Benutzers öffentlichzugänglich sind, denen also das Schlüsselwort ppppuuuubbbblllliiiicccc in ihrer Definition voransteht. Klassen mitZugriff protected, private oder "private package" (ohne spezielle Zugriffsdefinition) sinddemnach hier nicht mit aufgelistet. Interfaces und Exceptions werden gesondert aufgeführt.

Anzahl Klassen: 211

- Item-Klassen (Hilfsklasse für Containerstrukturen): 9

- konkrete Datentypen (ohne Item-Klassen): 4

- generische Basis-Datentypen (z. B. List2, Array1): 23

- statisch spezialisierte generische Basis-Datentypen: 64 -> davon semi-konkretisierte gen. Klassen (z. B. List2_Number): 12 -> davon konkretisierte gen. Klassen (z. B. List2_Int): 52

Benutzerhandbuch

26

- Typklassen: 53 -> bzgl. JDK-Basisdatentypen (z. B. T_Integer): 9 -> bzgl. Item-Klassen (z. B. T_Edge): 3 -> bzgl. konkreter GGL-Datentypen (z. B. T_DoubleVector): 4 -> bzgl. generischer Datentypen (z. B. T_List2): 23 -> bzgl. semi-konkretisierter gen. Datentypen (z. B. T_List2_Number): 8 -> bzgl. konkretisierter gen. Datentypen (z. B. T_List2_Int): 5- Werkzeugklassen: 5

- Graph-Algorithmen: 12

- Funktionsklassen abgeleitet von Interfaces (s. u.): 4

- Testprogramme: 28- sonstige Klassen: 6

- abstrakte Klassen (zwei abstrakte Typklassen): 2

- Exceptions: 3

Anzahl Interfaces: 7

An dieser Statistik sollten vor allem zwei Dinge auffallen. Zum einen die Anzahl statisch speziali-sierter Klassen (64) und zum anderen die der Typklassen (53). Sie machen zahlenmäßig mehr alsdie Hälfte aller Klassen aus, aber belegen einen weit geringeren Speicheranteil (ca. 500 KB). Siedienen oftmals nur der eindeutigen Argumentdefinition von Methoden und damit der Typsicher-heit.

3.2.2 Packages der GGL

Dieses Abschnitt zeigt den strukturellen Aufbau der GGL, der sich in acht Packages gliedert.

ggl.type Typ-Definitionen für generischer Klassen und Methoden

ggl.inst Instantiierungen generischer Klassen

ggl.basic Basis-Datentypen (Felder, Listen, Tupel, Wörterbücher, ..)

ggl.graph Die Klasse Graph und verwandte Datenstrukturen

ggl.graphAlg Graph-Algorithmen

ggl.graphIO Ein- und Ausgabe von Graphen in GGL- und GML-Format

ggl.util Verschiedene Hilfsklassen

ggl.test Testprogramme für Regressions- und Performancetest

- Package-Übersicht -

Benutzerhandbuch

27

Die Package ggggggggllll....ttttyyyyppppeeee enthält die für das generische Konzept der GGL notwendigen Typklassen,die als Parameter für die generischen Klassen (auch parametrisierten Klassen genannt) dienen, mitdenen sie auf einen oder mehrere Datentypen festgelegt werden. Mit ihr eng verbunden ist diePackage ggggggggllll....iiiinnnnsssstttt. In ihr befinden sich konkretisierte generischer Klassen, bei denen die Fest-legung auf den/die Sub-Datentyp(en) bereits statisch vorgenommen ist. Eine ausführliche Erläute-rung dieser beiden Packages, ihrer Inhalte und wie mit der GGL generisch programmiert werdenkann, befindet sich in Kapitel 3.4 „Generisches Programmieren mit Type“.

Die Package ggggggggllll....bbbbaaaassssiiiicccc enthält verschiedene generische Standardklassen und zwei mathemati-sche Datentypen, vor allem für Anwendungen der Linearen Algebra. Die generischen Standard-klassen umfassen unter anderem das ein- und zweidimensionale Feld, die doppelt verkettete Liste,Prioritätsschlangen und Wörterbücher. Außerdem enthält sie zwei Interfaces zum Vergleichen undSortieren, die Klasse mit den wenigen allgemeinen globalen Konstanten und eine spezielle Java-Exception für eine illegal ausgeführte Operation. Sie sind im API-Guide ausreichend dokumen-tiert.

Die Package ggggggggllll....ggggrrrraaaapppphhhh enthält die generische Klasse Graph, die allgemeine Klasse zur Imple-mentierung verschiedener Graphen. Außerdem enthält sie weitere graphbezogene Datentypen wiebeispielsweise NodeMap zur Abbildung einzelner Knoten oder EdgeMap zur Abbildung einzelnerKanten eines Graphen. In ihr enthalten ist auch die Klasse GraphGen mit einer Fülle verschiedenerGraphgeneratoren und die Klasse GraphMisc mit „kleinen“ Hilfsalgorithmen zum Überprüfen,Kopieren und Transformieren eines Graphen.

Die Package ggggggggllll....ggggrrrraaaapppphhhhAAAAllllgggg enthält die Menge der implementierten Graph-Algorithmen, wobeizu beachten ist, dass mehrere Basisalgorithmen bereits in der Klasse Graph selbst untergebrachtsind. Einige der Algorithmen sind generischer Natur, andere wiederum sind auf konkrete Daten-typen festgelegt. Eine weitere Beschreibung findet sich im API-Guide.

Die Package ggggggggllll....ggggrrrraaaapppphhhhIIIIOOOO enthält Klassen, die gemeinsam den erweiterbaren GML-Parserbilden, um einen Graph in GML-Format (siehe [Him97]) einzulesen. Die Routinen zum Schrei-ben eines Graphen in GGL- und GML-Format und zum Lesen in GGL-Format (GGL eigenesFormat) sind bereits in die Klasse Graph integriert.

Die Package ggggggggllll....uuuuttttiiiillll enthält verschiedene Werkzeugklassen, die vielerorts in der GGL genutztwerden. Hierzu gehört die Klasse Debug, mit deren Hilfe der Code leicht instrumentiert wurde, umdie Fehlersuche zu vereinfachen, die Klasse GglMath, die einige mathematische Operationen bereit-stellt, die Klasse InOut, die einige Operationen für die Ein-/Ausgabe enthält, die Klasse Tfw, dieden Rahmen für das integrierte Testsystem bildet und die Klasse Misc, die verschiedene kleineHilfsoperation enthält.

Die Package ggggggggllll....tttteeeesssstttt besteht aus einer Fülle kleiner Testprogramme, mit denen Benchmarksund Regressionstests ausgeführt werden können. Sie dienen zudem als kleine Beispielprogrammeund stellen in ihrer Gesamtheit, zusammen mit der Klasse Tfw (s. o.) das Testsystem dar.

Um einen guten Überblick über alle Klassen der GGL-Packages zu erhalten, sei neben dem API-Guide noch der Anhang A "Klassen der GGL" erwähnt. Dort sind die Packages nochmals, aber indiesem Fall über die Kurzbeschreibung aller enthaltenen Klassen, Interfaces und Exceptions,dokumentiert. Der Umfang der GGL wird dadurch differenzierter als in diesem Kapitel wider-gespiegelt.

Benutzerhandbuch

28

3.3 Allgemeines zur ImplementierungDieses Kapitel enthält einige Erläuterungen zu übergeordneten Themen der GGL, besonders ihreImplementierung betreffend.

3.3.1 Kompatibilität mit anderen Software-Bibliotheken

Die GGL basiert ausschließlich auf dem JDK 1.1.8 und ist ebenfalls mit JDK 1.3.1 und JDK 1.4.1getestet worden. Aufgrund der Tatsache, dass alle benötigen Klassen des JDK absolut adressiert imQuellcode importiert werden, das heißt, nicht in Form von Packages mit Wildcards (Bsp.: importjava.util.*), ist davon auszugehen, dass sie auch zukünftigen Versionen des JDK gerecht wird.Dies bedeutet, dass die GGL ohne Anpassung kompiliert werden kann und lauffähig ist, selbstwenn gleichnamige Klassen dem JDK hinzugefügt werden.

Falls verwendete JDK-Klassen in zukünftigen Versionen nicht mehr enthalten sind, muss natürlicheine Anpassung mit den Nachfolgern durchgeführt werden. Es werden in der GGL nur die abso-luten Standardklassen verwendet. Bei der Benennung der Klassen wurde trotz allem daraufgeachtet, dass alle verwendeten Klassennamen nicht im JDK vorkommen.

Da die GGL nur auf dem JDK aufbaut, ist auch davon auszugehen, dass sie mit allen anderenBibliotheken kompatibel ist, da ihr der kleinste gemeinsame Nenner genügt.

3.3.2 Programmierstil

Hier seien einige wichtige Aspekte des Programmierstils erwähnt, der in der GGL verwendetwurde, und der auf das Interface, das die GGL nach außen abgibt, wichtigen Einfluss hat.

Keine "Wrapper"-Methoden

Alle reinen "Wrapper"-Methoden wurden entfernt, d. h., dass die Methoden einer Klasse nur dieneu hinzugefügten widerspiegeln. Die Methoden der Superklassen, manchmal in dieser Doku-mentation auch Basisklassen genannt, müssen über den gesamten Ableitungsbaum dem Interfaceeiner Klasse als hinzugefügt betrachtet werden.

Methoden mit Default-Werten

Manche Klassen enthalten eine Methode mit gleichem Namen mehrfach (Overloading), wobei imVergleich zu anderen Methoden gleichen Namens nur das eine oder andere Argument fehlt. Anbei(im API-Guide und im Quellcode) ist dann oft nur eine Kurzdokumentation der Form "Same asxy with parameter 10 for yz argument".

Benutzerhandbuch

29

Dies bedeutet, dass hier eine C++-Funktion mit Default-Argumenten umgesetzt wurde, die ledig-lich mit dem gesetzten Argument die allgemeine Methode aufruft. Das "Same as" ist dann derAufruf, die allgemeine Methode für die Dokumentation hinzuzuziehen.

Ausbalancierter Code

Der Quellcode der GGL ist wohl strukturiert. Alle Verschachtelungen sind durch zwei zusätzlicheLeerzeichen am Anfang einer Zeile balanciert, wodurch sich der Code sehr gut lesen lässt. Jedegeschweifte Klammer führt zu einem Zeilenumbruch. Dadurch wird die Zeilenmenge imVergleich zu anderen Stils zwar erhöht, aber die Lesbarkeit auch. Ein Beispiel:

protected final void checkIndex(int i){if(i<lowIndex || i>highIndex)

{throw new IndexOutOfBoundsException("Index out of range: " + i +

"; valid range is " + lowIndex + ".." + highIndex); }

}

Im Code existieren keine Tabulatoren, sondern nur Leerzeichen zum Einrücken. Dadurch ist einegute Lesbarkeit unabhängig von unterschiedlichen Tabulatoreinstellungen garantiert.

3.3.3 Ein-/Ausgabe

Die GGL hat für die meisten ihrer Datentypen sehr gut definierte Operationen für die Ein- undAusgabe im Textformat. Stattdessen wurde auf den Weg über Serialization verzichtet, das heißt,kaum eine Klasse implementiert das Interface Serializable. Dieser Weg hat den Vorteil, dass dieAusgaben auch von Menschen leicht gelesen werden können. Die generischen Datentypen sindvon diesen Ein-/Ausgabeoperationen nicht ausgenommen und erlauben daher, in einfachster Artund Weise die Ein-/Ausgabe komplexester Strukturen über alle Hierarchie-Ebenen hinweg, ohnevon dem Benutzer nur eine Zeile selbstgeschriebenen Code zu verlangen. In Kapitel 3.4.2 wird einkleiner Vorgeschmack gegeben.

3.3.4 Randoms

Die Random-Funktionen der GGL, damit sind die gemeint, die die Pseudo-Zufallswerte beiBedarf produzieren, sind alle so instrumentiert, dass sie mit der Klasse ggl.util.Tfw (= Test-Framework) eng verknüpft sind und das sie, falls die Klasse Tfw es verlangt, die Werte, die sieproduzieren, entweder optional protokollieren oder aus einer Datei einlesen. Sie sind demnach mitder Klasse Tfw sehr eng verbunden. Sie befinden sich in der Klasse ggl.util.GglMath. DerPerformance-Verlust ist durch diese Instrumentierung minimal.

Dies sei hier nur deshalb erwähnt, damit der Benutzer darauf achtet, sich bei eigenen Programmenam besten auch an die Random-Funktionen zu halten, die ihm die GGL zur Verfügung stellt. So

Benutzerhandbuch

30

hat er die Möglichkeit mit wenigen Handgriffen, aus seinen eigenen kleinen Testprogrammen(siehe Kapitel 3.7) große Testserien und deterministische (nicht mehr durch unterschiedlicheZufallswerte variierende) Benchmarks zu erstellen.

3.3.5 Debuggen

Ich gebe zu, dieses Wort findet sich in keinem Lexikon, ich benutze es so aber schon seit Jahren.Gemeint ist natürlich die Fehlersuche und die Tätigkeit des "Ausbügelns" detektierter Fehler. Fallsdas für irgend jemanden Deutsch-Englisch-Kauderwelsch ist, so möge er mir verzeihen.

Die GGL war während ihrer Entwicklung sehr stark instrumentiert, um schnell Protokolle überdie internen Vorgänge zu erhalten und so entsprechend schnell die Fehler lokalisieren zu können(siehe Kapitel 2). Etwas davon ist in der jetzigen Version 1.0 noch enthalten - und das aus gutemGrund.

So existiert die Klasse ggl.util.Debug, die im großen und ganzen nur ein globales Flag hat, dasadvancedChecking heißt, und eine kleine Hilfsfunktion, die bei Bedarf verwendet werden kann.

Dieses Flag (vom Typ boolean) ist das Wichtigste an der Klasse. Es wird benutzt, um zusätzlicheinterne Überprüfungen zu steuern. Ist es auf false gesetzt, so sollten interne redundante Opera-tionen, die nur der Überprüfung von Ergebnissen einer Methode oder von Argumenten auf dierichtige Beschaffenheit dienen, aus dem Programmablauf ausgeschlossen werden. Das Pendant inC++ ist das meist genutzte Präprozessor-Makro #DEBUG.

Da wir hier ohne Präprozessor auskommen, ergibt sich durch die Instrumentierung des Codes einOffset von einer Fallunterscheidung pro Überprüfung, falls der gesamte Code eines Checks saubereingeklammert ist. Die reine Fallunterscheidung ist weitestgehend unerheblich für die Laufzeit, soergaben es zumindest meine Messungen. Somit ist diese Instrumentierung ähnlich praktikabel wieeine über einen Präprozessor gesteuerte.

In der GGL existieren viele interne Überprüfungen, die über dieses Flag an- und ausgeschaltetwerden können. Viele Graph-Algorithmen nutzen dieses Flag, um die Ergebnisse ihrer Algorith-men zusätzlich auf Korrektheit zu überprüfen. Die Dokumentation der einzelnen Methoden([WIN03]) gibt Auskunft, ob dieses Flag in ihnen in irgend einer Form zum Einsatz kommt.

Das bedeutet im Klartext: Für die Fehlersuche eines jeden Entwicklers sollte dieses Flag aktiviertsein über die einfache Zeile:

ggl.util.Debug.advancedChecking = true;

an einer beliebigen Stelle im Code. Alles nachfolgende wird dann, sofern das Flag relevant ist, einerbesseren Kontrolle unterzogen. Das Ausschalten geht analog. Die GGL wird dann bei einemFehler automatisch eine RuntimeException mit Detailinformation ausgeben. Der Stackdump gibtdaraufhin schnell Aufschluss über die Stelle, an der das Problem auftrat.

Benutzerhandbuch

31

3.4 Generisches Programmieren mitType

Dieses Kapitel beschreibt das Konzept zur generischen Programmierung mit Java, das für die GGLentwickelt wurde, in ihr Verwendung findet und von der GGL fundamental unterstützt wird.Anhand von einigen Beispielen werden Facetten und Möglichkeiten dieses generischen Konzeptsverdeutlicht.

Der Ausdruck "generisches Konzept" kann hierbei auch im doppeldeutigen Sinne gesehen werden.Im allgemeinen verstehen wir hierunter den Bauplan und die Mittel zum Erstellen generischerDatentypen und Algorithmen. Zum anderen lässt sich dieses Konzept aber auch auf andereobjektorientierte Programmiersprachen als nur Java anwenden und ist damit zudem in sichgenerisch.

Im nun folgenden wird das Konzept anhand der konkreten Implementierung beschrieben. DerKlasse ggl.type.Type (kurz: Type) fällt dabei eine Schlüsselrolle zu, wie am Titel dieses Kapitelsleicht erkennbar. Hier wird das Konzept und die Implementierung so vorgestellt, dass man sieschnell versteht und mit ihr arbeiten kann. Des öfteren wird hierbei auch C++ angesprochen, da esdas Ziel dieses Konzepts war, mindestens die gleichen Möglichkeiten zu erreichen, wie die durchC++ Templates gegebenen. Kenntnisse in C++ sind daher von Vorteil. Als Buchempfehlung sei derKlassiker "The C++ Programming Language, Second Edition" [Str91] genannt.

Um es noch einmal zu verdeutlichen: Der Grund für dieses generische Konzept, die generischeProgrammierung als solche, liegt darin, die Programmierarbeit so produktiv wie möglich zugestalten, indem effizienter, auf Wiederverwendbarkeit und Wartbarkeit ausgelegter Code produ-ziert werden kann. Die GGL wäre um einiges größer bzgl. Speicherbedarf, ineffizienter bzgl. deseingesetzten Quellcodes und des Laufzeitverhaltens und erheblich eingeschränkter bzgl. ihrerVerwendbarkeit, wäre nicht von diesem Konzept Gebrauch gemacht worden.

3.4.1 Das generische Konzept

Unter generischer Programmierung mit der GGL verstehen wir Klassen und Methoden in einerallgemeingültigen Form so zu programmieren, dass eine Menge der in ihnen verwendeten Daten-typen (im folgenden Subtypen genannt) vorerst nicht konkretisiert, d. h., auf spezielle Datentypenfestgelegt ist.

Um dies zu realisieren, kommen so genannte Typparameter zum Einsatz, die als Platzhalter für dieDeklaration der Subtypen dienen. Diese werden bei einer generischen Klasse erst dann aufkonkrete Datentypen gesetzt, wenn man sie verwenden möchte und damit die Ausrichtung derSubtypen bekannt ist.

Eine Containerklasse kann beispielsweise generisch ausgerichtet sein, indem der Datentyp derenthaltenen Elemente über einen Typparameter bestimmt wird. Eine Methode zur Berechnungeines numerischen Ergebnisses kann in gleicher Weise generisch programmiert sein, indem derZahlentyp, der der Berechnung zugrunde liegt, über einen Typparameter festgelegt wird. Man

Benutzerhandbuch

32

spricht auch von typparametrisierten Klassen. Im Prinzip ist diese Art der generischen Program-mierung die gleiche, wie sie in C++ durch den Einsatz von Templates praktiziert wird. Der Wegdahin ist jedoch ein anderer. Deshalb nennen wir die Konkretisierung einer generischen Klasse imallgemeinen auch nicht Instantiierung wie in C++, sondern Spezialisierung. Für C++ macht esSinn immer von Instantiierungen zu sprechen, da aus den Templates tatsächlich Code-Instanzengeneriert werden, nachdem eine Textersetzung der Typparameter mit den verwendeten konkretenDatentypen stattgefunden hat. Für die Typparameter werden also einfach die Namen der Sub-typen eingesetzt und den Rest besorgt der Compiler.

Da Java die nötigen Spracherweiterungen und Mittel für typparametrisierte Klassen derzeit nochnicht mitbringt, obwohl sie seitens der Urheber bereits geplant sind, verwenden wir in und mit derGGL zur Spezialisierung generischer Klassen spezielle Typklassen anstelle der Datentypen selbst.Das heißt, dass für jeden Datentyp, mit dem eine generische Klasse spezialisiert werden soll, eineparallele, zur Klasse des Datentyps korrespondierende, Typklasse verwendet wird, die völlig unab-hängig programmiert werden kann. Das folgende Bild illustriert diesen Zusammenhang.

Generische Klasse

Spezielle Typklasse

Spezialisiertegenerische

Klasse

Korrespondierender Datentyp

- Das generische Grundprinzip -

Damit das ganze System funktioniert, sind in den Typklassen Methoden zum Ausführen derStandardoperationen bzgl. des korrespondierenden Datentyps enthalten. Die generische Klassenutzt ausschließlich diese Operationen, um mit Objekten des Datentyps zu arbeiten. Hierzugehört vorrangig das Erzeugen und Kopieren eines Objekts des Datentyps. Das Standardinterfacealler Typklassen umfasst aber zusätzlich auch das Vergleichen von Objekten, eine Hash- und eineOrdnungsfunktion sowie Methoden für die Ein- und Ausgabe. Spezielle Typklassen können diesesInterface beliebig erweitern, um den Ansprüchen des Datentyps zu genügen. So existiert in derGGL beispielsweise eine Typklasse für Zahlentypen, die zusätzlich die gängigen numerischenOperationen enthält.

Das also, was der C++-Compiler bei Templates implizit macht - die richtige Verknüpfung derElemente abhängig vom Datentyp - das wird mit diesem Konzept für Java explizit über die Typ-klassen bestimmt. Sicher ist hierfür etwas mehr Aufwand erforderlich, sofern eine generische Klasse

Benutzerhandbuch

33

oder eine Typklasse in der GGL noch nicht existiert und daher neu erstellt werden muss. Aberdurch das Framework, das die GGL bietet, und mit den Richtlinien zur Erstellung, die in dennächsten Abschnitten zu lesen sind, dürfte dies nicht schwer fallen. Diese Richtlinien zu beachtenist wichtig, damit das Gesamtkonzept nicht aus den Fugen gerät. Belohnt wird man dafür invielerlei Hinsicht. Beispielsweise ist es plötzlich kein Grauen mehr, in generischen Klassen zudebuggen. Auch bietet dieses Konzept ein Maximum an Flexibilität. Zum Beispiel können generi-sche Klassen dynamisch wie statisch spezialisiert werden und die Code-Redundanz, wie sie bei C++Templates nach wie vor vorkommt, wird auf ein absolutes Minimum reduziert.

Sich auf dieses Konzept einzulassen macht auch deshalb Sinn, weil es auf den Grundprinzipienobjektorientierter Programmierung beruht und damit mit allen Java-Frameworks kompatibel ist.Es lässt sich analog auf andere objektorientierte Sprachen übertragen und ist für den Entwicklertransparent und leicht nachvollziehbar. Damit wird es hoffentlich immer noch seine Daseinsbe-rechtigung haben, wenn die Spracherweiterung für generische Datentypen seitens Sun Einzug hält.Der Vorteil dieses Konzepts, im Vergleich zu anderen Ansätzen, ist, dass es keinen Pre-Compilerbenötigt, sondern direkt in der Sprache Java funktioniert.

3.4.2 In der Anwendung

Ein kurzes Beispiel soll nun ansatzweise zeigen, was alles mit dem generischen Konzept möglich istund ein Gefühl für die Arbeit damit vermitteln.

// Example 1: Introduction generic concept

import java.io.*;import ggl.basic.*;import ggl.type.*;import ggl.util.*;

public final class Example01{

public static void main(String[] args) throws IOException { // 1. Creation: Create generic list, dynamicly specialized for Integer List2 list = new List2(T_Integer.type());

// 2. Show default initialization System.out.println("list right after construction"); System.out.println(list); System.out.println();

// 3. Init with Integer objects list.pushBack(new Integer(3)); list.pushBack(new Integer(4)); list.pushBack(new Integer(1)); list.pushBack(new Integer(5)); list.pushBack(new Integer(2));

// 4. Show initSystem.out.println("list after initialization");

Benutzerhandbuch

34

System.out.println(list); System.out.println();

// 5. Sort list by default linear ordering list.sort();

// 6. Show result of sorting System.out.println("list after sorting"); System.out.println(list); System.out.println();

// 7. Print list to a file PrintWriter out = InOut.getPrintWriter("test_list_io"); list.write(out); out.close();

// 8. Create new list and read from file List2 list2 = new List2(T_Integer.type()); PushbackReader in = InOut.getPushbackReader("test_list_io"); list2.read(in); in.close();

// 9. Show result of read list System.out.println("list2 from file"); System.out.println(list2); System.out.println();

// 10. Compare both list and show result System.out.println("result of comparison"); System.out.println(list.compare(list2)); System.out.println(); }

};

/*-------------------------------------Output:---------------------------------------list right after construction

list after initialization3 4 1 5 2

list after sorting1 2 3 4 5

list2 from file1 2 3 4 5

result of comparison0---------------------------------------*/

Benutzerhandbuch

35

Die Kommentare machen den Code sicher leicht verständlich - nicht zuletzt sind die Namen derMethoden selbstredend - trotzdem hier noch ein paar zusätzliche Erklärungen, um die Zusam-menhänge zu verstehen:

Gezeigt wird, wie ein Objekt list der generischen Listenklasse List2 erzeugt wird (1), und zwardynamisch spezialisiert mit der Typklasse T_Integer (genauer gesagt mit der Standardinstantiie-rung der Typklasse T_Integer, dazu aber später mehr in Kapitel 3.4). Das bedeutet, dass dasObjekt list eine Liste ist, die gemacht ist für Einträge der Klasse Integer.

Über die Typklasse T_Integer werden die Standard-Operationen für den Datentyp Integer,parallel zu der Klasse Integer selbst, in Form von Methoden der Typklasse definiert. Hierzugehört z. B. die Definition einer linearen Ordnung, Operationen für die Ein-/Ausgabe, eineKopieroperation und weitere. Diese Operationen werden in der generische Klasse List2 genutzt,um durch wenige Zeilen Code große Operationen auszuführen.

Nach der Erzeugung des Listenobjekts wird die automatisch durchgeführte Initialisierung der Listegezeigt (2). Die ist recht unspektakulär, da dies die leere Liste ist. Daraufhin wird sie mit Integer-Elementen gefüllt (3) in unsortierter Reihenfolge bzgl. der linearen Ordnung des DatentypsInteger (3, 4, 1, 5, 2). Wieder wird die Liste gedruckt (4), um das Ergebnis anzuzeigen ("3 4 1 52"). Dies wird über die toString-Methode der Klasse List2 erreicht, die, falls vorhanden, dortaufgerufen wird, wo ein String-Objekt erwartet wird. Dieser Mechanismus ist bereits in Javaimplementiert. Aber was passiert dann? In der Methode toString wird eine Konkatenation derErgebnisse durchgeführt, die die Typklasse T_Integer für jedes Integer-Objekt erzeugt, indem ihretoString-Methode, mit den Integer-Objekten als Argument, aufgerufen wird. Hierdurch wirdjedes Objekt der Klasse List2 immer richtig gedruckt, sofern nur die Typklasse, mit der sie spezia-lisiert wurde, eine korrekte toString-Methode für ihre korrespondierende Klasse (in diesem FallInteger) enthält.

Dies ist also das Grundprinzip der generischen Programmierung der GGL, in Kurzform und starkvereinfacht dargestellt. Genauso funktionieren auch die anderen, darauf folgenden Zeilen Code.

Nach der manuellen Initialisierung der Liste (3) wird sie sortiert (5), indem in dem Sortier-algorithmus der Klasse List2 (Quicksort) die Vergleichsoperationen der Typklasse benutztwerden. Daraufhin wird sie zur Kontrolle erneut ausgegeben (6) und danach im GGL-Format indie Datei "test_list_io" geschrieben (siehe Kapitel 3.3.3 "Ein-/Ausgabe"). Das Prinzip, wie diesalles funktioniert, ist das gleiche wie bei der toString-Methode.

Danach wird eine neue Instanz der generischen Klasse List2 erzeugt (8) namens list2, wiederumspezialisiert für Objekte der Klasse Integer, aber diesmal handelt es sich um die statisch speziali-sierte Variante List2_Int. In ihr ist die Typklasse bereits statisch fest verankert und wird nicht erstwährend der Konstruktion zugewiesen. Die vorher geschriebene Datei wird nun von ihr eingelesenund daraufhin wird sie, wie die vorherige Liste, zur Kontrolle ausgegeben (9). Und siehe da, es hatfunktioniert.

Zum Abschluss werden diese beiden Listen nochmal mittels der Methode compare verglichen (10)und durch das Resultat 0 als identisch ausgewiesen. Auch in diesem Fall wurde die Operation aufdie Typklasse "abgewiegelt", indem die compare-Methode der Klasse List2 die Einträge einennach den anderen über die compare-Methode der Typklasse vergleicht.

Benutzerhandbuch

36

3.4.3 Die Klasse Type

Die Klasse gg.type.Type ist eine ganz spezielle und sehr durchdachte Konstruktion zur Gewin-nung von Typklassen, durch die die generische Programmierung im Vergleich zu anderen"Ansätzen" wie denen mit Interfaces (siehe Kapitel 2.1.4) oder Pre-Compilern, erst ermöglichtwird. Auf die wichtigsten Merkmale wird nun eingegangen, um zu verstehen was zu beachten ist,wenn eigene Typklassen erstellt werden.

Im Kapitel 3.4.1 wurde bereits das Grundkonzept erläutert und die Klasse Type dabei angespro-chen. Die Idee ist demnach, eine spezielle Typklasse zu verwenden, um damit Klassen zu speziali-sieren. Diese müssen natürlich (und so schließt sich der Kreis) auf die Typklassen ausgelegt sein,sprich, generisch entworfen worden sein.

Type als Basis-Interface

Durch die Klasse Type wird das Basis-Interface gegeben, das alle abgeleiteten Klassen automatischerben (ob sie wollen oder nicht) und das die generische Klasse verwenden kann, um mit einergewissen Sorte von Objekten zu operieren. Bei den meisten Containerklassen beziehen sich alleElemente auf einen einzigen Datentyp, d. h., sie benötigen genau eine Typklasse als Parameter, umgenerisch ausgerichtet zu werden.

Wenn der Quellcode der Klasse Type studiert wird, erkennt man, dass sich die in ihr enthaltenenMethoden in zwei Abschnitte aufteilen. Der erste Abschnitt betrifft die Klasse Type selbst, auf diespäter eingegangen wird, und der zweite Abschnitt bildet das Interface ab, das die generische Klassefür Operationen auf den Elementen benutzen kann. Diese Abschnitte sind durch folgende Markie-rungen kenntlich gemacht:

////////////////////////////////////////////////////////////////////////////////////////// Type part ////////////////////////////////////////////////////////////////////////////////////////

für die Operationen und Inhalte die die Klasse Type selbst betreffen und

////////////////////////////////////////////////////////////////////////////////////////// Class part ////////////////////////////////////////////////////////////////////////////////////////

für die von generischen Klassen und Methoden nutzbaren Operationen, die die Klasse des korres-pondierenden Datentyps betreffen. Sie stellen das nutzbare Interface dar.

Wie schon gesagt, enthält die Klasse Type das Basis-Interface, ist aber nicht als Interface oder echteabstrakte Klasse definiert. Zur Verdeutlichung, dass man hier bestimmte Operationen überschrei-ben muss, ist sie zwar in ihrem Klassenkopf als abstrakte Klasse definiert, enthält aber keine einzigeabstrakte Methode. Der Clou liegt auch darin, dass sie durch diese Deklaration selbst nicht alsgültige Typklasse verwendet werden kann.

Dafür, dass es keine abstrakte Methode in ihr gibt, gibt es einleuchtende Gründe. Als Interfacedefiniert wäre es ihr nicht möglich irgendwelchen Code zu enthalten. Mit abstrakten Methodendefiniert, müssten alle abgeleiteten Typklassen zwanghaft die gesamte Menge der in ihr definierten

Benutzerhandbuch

37

Basis-Operationen implementieren, auch wenn sie diese gar nicht implementieren müssen, weil siean eine Verwendung anknüpfen, die nicht benötigt wird.

Dieser Zwang wurde deshalb aufgeweicht, weil die Klasse Type alle Operationen für das Basis-Interface bereits definiert. Die Definition ist entweder allgemeingültig ausgelegt, so dass sie vonmöglichst wenigen abgeleiteten Klassen überschrieben werden muss oder die Default-Implementie-rung tut nichts anderes als eine AbstractMethodError-Exception zu werfen. Oder es handelt sichum von den Basis-Operationen abgeleitete Operationen, die immer gültig sind, sofern die dafürbenötigten Basis-Operationen richtig implementiert sind. Diese können für Optimierungszweckeüberschrieben werden, müssen es aber nicht. Hierzu gehört z. B. itemRead und itemWrite, die aufden Methoden read und write basieren und eine in Tokens eingebettete Ein-/Ausgabe realisieren.Sie dienen als Hilfsmittel, um komplexe Strukturen in einer sicheren und einfachen Form alsTextdatei ein- und auszugeben (siehe auch Kapitel 3.3.3).

Die wichtigsten Grundoperationen, die in ihrer Implementierung kontrolliert werden müssen,sind createDefault und copy. Ohne die korrekte Implementierung dieser beiden geht in dengenerischen Containerklassen gar nichts. Die Methode createDefault wird immer dann genutzt,wenn ein Objekt der korrespondierenden Klasse erzeugt werden soll. Wie z. B. bei der Initialisie-rung der generischen Klasse Array1, wo jedes Element über diese Operation initialisiert wird. DieMethode copy(Object x) dient in diesem Beispiel dazu, ein Element zu kopieren. Hierüberwerden auch die Deep-Copies realisiert. Die Default-Implementierung kopiert nur den Referenz-wert des übergebenen Objektes, was aber für viele Datentypen wie String, Integer, Double oderauch Object völlig ausreicht und bei den korrespondierenden Typklassen (T_String, T_Integer,T_Double und T_Object) daher kein Überschreiben der Methode notwendig macht.

Um nun nicht den gesamten Quellcode der Klasse Type hier abzudrucken und in gleicher Weise zukommentieren, wie es dort bereits geschehen ist, sei der Leser gebeten, die DateiGGL100/src/ggl/type/Type.java aufzurufen und sich die beiden oben genannten Bereicheanzusehen.

3.4.4 Typklassen

Um es noch einmal zu wiederholen: Typklassen werden immer von der Klasse ggl.type.Typedirekt abgeleitet oder von einer anderen Typklasse. Der Grundaufbau ist immer derselbe undgliedert sich immer in die zwei o. g. Bereiche, bei denen der eine Bereich sich auf die Typklasseselbst bezieht und der andere auf die zu der Typklasse korrespondierenden Datentyp-Klasse. Sieenthält die Typdefinition für eine spezielle Klasse, die einen Datentyp repräsentiert.

Da Vererbung für Typklassen die gleiche Bedeutung hat, wie für "normale" Klassen, hier ersteinmal eine kleine Illustration, die eine Verfeinerung eines Teils des in Kapitel 3.4.1 dargestelltenGrundkonzepts ist:

Benutzerhandbuch

38

Spezielle Typklasse

Korrespondierender Datentyp

classType

- Die spezielle Typklasse -

Diese Illustration zeigt den Grundzusammenhang zwischen einer speziellen Typklasse, abgeleitetaus der allgemeinen Typklasse Type und der Assoziation zum korrespondierenden Datentyp (= zurkorrespondierenden Klasse).

Nun betrachten wir eine Auswahl von Typklassen und nehmen sie etwas genauer unter die Lupe.Der Leser sollte nach Beendigung dieses Abschnitts in der Lage sein, selbst Typklassen zu erstellen,wobei ein Blick in den Quellcode dieser Klassen wiederum angenommen wird, um ein vollständi-ges Bild zu erhalten (siehe GGL100/src/ggl/type/*). Die wichtigsten Aspekte für Typklassen, diezu konkreten Datentypen korrespondieren, seien hier erläutert. Den generischen Typklassen, diezu generischen Klassen korrespondieren, wird ein weiterer Abschnitt gewidmet.

Eine Auswahl an Typklassen:

- Type Basisklasse aller Typklassen

- T_Objekt Typklasse für Object

- T_String Typklasse für String

- T_Number Typklasse für Number

Beginnen wir mit TTTT____OOOObbbbjjjjeeeecccctttt. Dies ist eine sehr spezielle Typklasse, die die in ihr definiertenOperationen in erster Linie auf die Methoden, die der Klasse Object von Natur aus mitgegebensind (toString, hashCode, etc.), stützt.

Sie ist wichtig, um generische Klassen so zu spezialisieren, dass sie sich so verhalten, wie dieStandard-Containerklassen des JDK (z. B. java.lang.Array). Mit ihr werden generische Datenty-pen Shallow-Copies ausführen, sofern sie kopiert werden und sich immer mit dem Referenzwertnull initialisieren. Nur durch sie ist es beispielsweise möglich, eine generische Containerklasse mitObjekten unterschiedlicher Klasse zu füllen, sofern nur über eine Typklasse spezifiziert wird.

Benutzerhandbuch

39

Es gilt die Konvention, dass eine generische Klasse für die Elemente, auf die sich ein Typpara-meter bezieht, nur Objekte der Klassen zulassen darf, zu der die Typklasse korrespondiert, die fürdie Spezialisierung benutzt wird (siehe Abschnitt 3.4.7 "Generische Klassen"). Dies schließtabgeleitete Klassen nicht aus. Die Besonderheit für T_Object ist die, dass sich jede Klasse von derkorrespondierenden Klasse Object ableitet.

T_Object ist auch deshalb eine spezielle Konstruktion, weil es etwas abseits vom allgemeinenAbleitungsbaum der Klasse Type steht. Von ihr gehen keine Äste ab, sie ist also ein Blatt im Baum,das direkt von Type abgeleitet wird. Folgendes Bild veranschaulicht dies:

T_Object

java.lang.ObjectclassType

T_String T_Number

T_Integer

java.lang.String

- Type-Ableitungsbaum (Ausschnitt) -

Allgemein gilt die Regel, dass Typklassen, um ein in sich schlüssiges System zu erhalten, möglichstden gleichen Ableitungsbaum haben sollten, wie die korrespondierenden Datentyp-Klassen. Dieserweitert die Flexibilität und erleichtert sehr den Aufbau der Typklassen.

In manchen Fällen ist es allerdings nicht praktikabel oder erwünscht. Man erkennt, dass z. B. vondieser Regel bzgl. der Typklasse T_Object Abstand genommen wurde. Obwohl sich alle Java-Klassen von der Klasse Object ableiten, sieht man im dargestellten Ableitungsbaum, dass stattdes-sen die Typklasse Type die allgemeine Wurzel ist.

Dies wurde aufgrund der Sonderstellung von T_Object so angelegt. Es ist eine Optimierung, hältden Ableitungsbaum eine Stufe flacher und geht mit den vorimplementierten Methoden in Typebesser auf die Bedürfnisse der anderen Typklassen ein, als wenn sie alle das spezielle Set anMethoden von T_Object überschreiben müssten. Es resultiert in einem besseren Laufzeitverhalten.

Es gilt deshalb hiermit die Regel, dass alle Typklassen, deren korrespondierende Klasse sich direktvon der Klasse Object ableitet, direkt von der Klasse Type abgeleitet werden sollen.

Benutzerhandbuch

40

Kommen wir zu TTTT____SSSSttttrrrriiiinnnngggg. T_String ist leicht zu erkennen als die Typklasse für die KlasseString. Das liegt wohl daran, dass die Naming-Convention, die hier benutzt wurde, bereitsverstanden wurde:

Es gilt: Eine Typklasse anders als Type beginnt immer mit "TTTT____" und darauf folgt der Klassennamedes Datentyps. Falls er zu lang ist, wird er abgekürzt. Es gelten auch immer die Abkürzungen, diebereits genutzt wurden, um Konsistenz zu erreichen. Manche Fälle bedingen hierbei allerdings eineAusnahme. Diese Regel wird noch ausgebaut, sobald die generischen Typklassen miteinbezogenwerden.

Zu T_String sei nun noch angemerkt, dass die lexikographische Ordnung in der Type-Methodecompare integriert ist. Nun behandeln wir noch kurz den Zahlentyp T_Number und die TypklasseT_Integer für Integer-Objekte.

Die Klasse TTTT____NNNNuuuummmmbbbbeeeerrrr ist die Basisklasse aller Zahlentypen. Sie erweitert das Interface, also das Setan Operationen, wie es im übrigen jede Typklasse tun kann. T_Number korrespondiert zu demInterface java.lang.Number, das von allen JDK 1.1.8-Zahlentypen implementiert wird. DieKlasse ist ebenfalls und aus den gleichen Gründen wie die Klasse Type als abstrakt deklariert, ohneabstrakte Methoden zu beinhalten. Sie enthält alle mathematischen Grundoperationen und wirddaher mit Vorliebe in generischen numerischen Algorithmen eingesetzt, die auf diesen Methodenoperieren und damit mithilfe der abgeleiteten Zahlen-Typklassen T_Integer, T_Double, T_Floatetc. benutzt werden können, was heißt, dass sie die Ergebnisse in dem gewünschten Zahlenformatdurchführen.

TTTT____IIIInnnntttteeeeggggeeeerrrr,,,, wie auch die anderen Zahlenklassen, enthalten aufgrund ihrer starken Verwendungviele zusätzliche Methoden nur aus Optimierungszwecken. D. h. sie überladen Operationen derKlasse T_Number, obwohl diese Gültigkeit besitzen.

Wenn eigene Zahlenklassen kreiert werden, dann sollten auf jeden Fall die bestehenden als Vorlageverwendet werden. Damit geht es sehr zügig voran und man bewegt sich auf sicherem Boden.

3.4.5 Instantiierung von Typklassen

Unnötige Instanzen von Typklassen, also Objekte von Typklassen, sollten nicht erzeugt werden.Dies gilt für andere Klassen natürlich genauso. So nimmt beispielsweise der Compiler schon eineOptimierung in Form einer Bündelung aller gleichartig erzeugter Integer-Objekte vor. Das heißt,dass zwei Integer-Instanzen wie ein zweimal vorkommendes "new Integer(3)" im Codeletztendlich zu einem Objekt gebündelt werden. Dies ist möglich, da ein Objekt der Klasse Integernicht verändert werden kann. Die Objektreferenz zu dem Objekt kann somit als Stellvertreter fürden Wert 3 überall im Code verwendet werden. Falls ein Objekt verändert werden kann, ist diesnatürlich ausgeschlossen.

Dieses Konzept, dass Objekte der Klassen String, Integer, Double, Char usw. nicht verändertwerden können, erlaubt erst den stabilen Einsatz von den JDK-Containerklassen, die nur aufObjekt-Referenzwerten agieren.

In der GGL wird von diesem performance-steigernden Konzept (u. a. durch die bessere Cache-Ausnutzung) ebenfalls Gebrauch gemacht, indem die Typklassen, die konkrete Datentypen

Benutzerhandbuch

41

betreffen, nur einen private-Konstruktor besitzen und damit auf die üblich Art von außen nichtmehr instantiiert werden können. Dieser Konstruktor wird für die Typklassen nur einmal undzwar in der Typklasse selbst statisch aufgerufen. Das bedeutet, diese Klassen sind selbstinstantiie-rend. Von außen kann man dann das, die Klasse repräsentierende Objekt nur über die Methode".type()" erreichen (siehe das Code-Beispiel in Kapitel 3.4.2). Dieses Konzept betrifft inabgewandelter Form auch generische Typklassen, dazu aber später mehr.

Im Vergleich zu C++ sieht man die großen Unterschiede. In C++ werden die Datentypen selbst fürdie Konkretisierung eines generischen Konstrukts benutzt (C++ Templates). In diesem Konzeptfür Java wird stattdessen ein Objekt als Repräsentant seiner Typklasse verwendet, die wiederum einRepräsentant für den tatsächlichen Datentyp ist. Mit diesem Absatz sollte man sich anfreunden,dann hat man das wesentliche Grundprinzip verinnerlicht.

3.4.6 Generische Methoden

Bevor es zu dem Thema "Generische Klassen" kommt, eine kurze Vorstellung generischerMethoden anhand eines kleinen Beispiels.

void doSomething(Type theType, Object x){System.out.println(theType.toString(x));}

Was wir hier sehen, ist eine der einfachsten generischen Methoden. Sie gibt über System.out dieString-Repräsentation des Objekts x über die Typklasse theType aus. Wie im vorherigenAbschnitt erläutert wurde, ist es erlaubt, so zu sprechen, obwohl ein Objekt der Typklasse Typedafür verwendet wird, da das Objekt die Klasse vollständig repräsentiert und auch nur für diesenZweck instantiiert worden ist.

Die übergebenen Argumente theType und x müssen bei der Verwendung von doSomethingzueinander passen. Wie schon in der Erläuterung des Grundkonzepts erwähnt, korrespondiert eineTypklasse zu der Klasse des Datentyps für den sie gemacht wurde. Gültige Argumentpaare fürtheType/x sind beispielsweise Objekte der Klassen T_String/String, T_Integer/Integer undT_Object/Object.

Die toString-Methode in der Basis-Typklasse Type, die auch von der abgeleiteten TypklasseT_Objekt unverändert übernommen wurde, sieht folgendermaßen aus:

String toString(Object x){return x.toString();}

Wird also T_Object für das Argument theType in doSomething eingesetzt, dann ist die Methodeidentisch mit der klassischen Variante:

void doSomething2(Object x){System.out.println(x.toString());}

Benutzerhandbuch

42

Wofür nun dieser Aufwand? Der Unterschied von doSomething zu doSomething2 liegt imwesentlichen darin, dass über den Umweg über die Typklasse vorerst eine Abkopplung vonMethoden der Zielklasse passiert, wodurch zusätzliche Freiräume offeriert werden. DieseParallelkonstruktion erlaubt daher Dinge, wie sie vom Operator-Overloading in C++ her bekanntsind. Da eine Typklasse völlig unabhängig konstruiert werden kann, also nicht über Vererbungoder Sonstiges an die Klasse für die sie gemacht wurde, gebunden ist, kann sie also auch eine völligandere Ausgabe produzieren. Dieser Vorteil offenbart sich beispielsweise, wenn die Methode miteinem Objekt einer Klasse verwendet werden soll, die nur eine unzureichende toString-Implementierung mit sich bringt (z. B. die schlichte Default-Implementierung von Object). Diefür diese Klasse erstellte korrespondierende Typklasse kann dann dieses Manko durch eineadäquate Implementierung korrigieren. Gleiches gilt analog für alle anderen Methoden, die vonder Typklasse (in diesem Fall Type) für Operationen bereitgestellt werden.

Die eben vorgestellte Variante, eine generische Methode zu gestalten ist nur ein schulischesBeispiel. Tatsächlich sehen die meisten völlig anders aus, da die Typklasse implizit übergeben wird:

Number sumElements(List2_Number x){T_Number numType = x.getNumType();Number sum = numType.zero();

for(Enumeration it = x.elements(); it.hasMoreElements();){List2.Node v = (List2.Node) it.nextElement();sum = numType.add(sum, x.inf(v));}

return sum;}

In diesem Beispiel wird eine Liste von Zahlen aufsummiert. Die Operationen, die dafür nötig sind,werden über die Typklasse für Zahlen realisiert, die aus der generischen Liste für Zahlen abgerufenwerden kann.

Auch hier sei wieder angemerkt, dass der Quellcode und die API-Dokumentation die bestenBeispiele liefern, effizient Algorithmen umzusetzen. Der Aufwand ist, wie man sieht, etwas höher,da nicht die nativen Operationen für einen bestimmten Zahlentyp genutzt werden können.Allerdings ist es sehr einfach, einen existierenden Algorithmus in einen generischen zu verwandeln,da nur die Operatoren und Konstanten durch Operationen der Typklassen ausgetauscht werdenmüssen.

Die großen Vorteile ergeben sich bei diesem Mehraufwand im Nachhinein, da für einen neuenDatentyp, ist die Typklasse erstmal erstellt, das ganze Spektrum an verfügbaren Algorithmengenutzt werden kann. Kurzum, am Ende steht ein Gewinn.

Benutzerhandbuch

43

3.4.7 Generische Klassen

Generische Klassen werden in erster Linie für Datentypen gebraucht, können aber auch für eineMenge von Algorithmen genutzt werden, die in einer Klasse kumuliert sind. Letztendlich ist es derPhantasie des Lesers überlassen, wofür er eine Typparametrisierung einsetzten möchte. BeiContainerklassen spielen sie ihre größten Stärken aus. Vor allem, weil generische Containerklassenwiederum, auf einfache Art und Weise, mit einer dafür gemachten Typklasse in andere Containereingesetzt werden können. Das ist aber noch nicht alles. Generische Datentypen der GGL könnendynamisch wie statisch spezialisiert werden. Das bedeutet, ein Algorithmus kann Datenstrukturenentwickeln.

Als Beispiel sei nun die Klasse Array1 begutachtet. Durch sie wird ein generisches eindimensiona-les Feld realisiert. Uns interessiert jetzt nicht, wie viele Methoden sie hat oder Ähnliches. Amwichtigsten sind die Konstruktoren und wo und wie die Typklasse gesetzt wird, die für dieElemente als Typdefinition dient, also die Operationen definiert, die auf ihnen in dieser Klasseausgeführt werden.

public class Array1 implements Cloneable{/*** The element type.*/protected final Type elemType;

/*** Returns my element type, the Type object which determines* the class and behaviour of my elements.*/public final Type getElementType()

{ return elemType; }

..

Wir sehen nur den Klassenkopf. Dass Array1, das Interface Cloneable implementiert, ist nicht vonBelang. Von größtem Interesse sollte sein, wie der Elementtyp elemType gespeichert wird. Durchdie Definition als protected final, kann er nicht mehr verändert werden, nachdem er gesetztwurde. Das schafft Sicherheit und erlaubt dem Compiler bei statischen Spezialisierungen, was imKapitel 3.4.8 behandelt wird, eine Optimierung vorzunehmen.

Der Elementtyp kann über die Methode getElementType jederzeit abgerufen und wiederverwendetwerden, wie wir es bereits im Abschnitt 3.4.6 am Beispiel der Methode getNumType gesehen haben,wo eine Methode den Zahlentyp einer numerischen Listenklasse abfragt und benutzt.

Nun zu dem zweitwichtigsten Element, dem Konstruktor, der lediglich den Typparameterbekommt. Durch diese Konstruktion wird der Elementtyp gesetzt. Danach wird die Standard-initialisierung für das leere Feld durchgeführt, über die Initialisierungsmethode mit demstandardisierten Namen init.

Benutzerhandbuch

44

// --------------------------------------------------// constructors// --------------------------------------------------

/*** Constructs a new Array1 object with elements of type elemType* (i.e., elements of class corresponding to elemType) and empty domain* (= empty index set). <br>* Use one of the init methods to get it initialized to your demands.*/public Array1(Type elemType)

{ this.elemType = elemType; init(); }

Dies ist der standardisierte Weg, um ein Objekt einer generischen Klasse zu erzeugen. DieInitialisierung, wie sie bei gewöhnlichen Klassen bereits über die Konstruktoren vorgenommenwird, wird durch den nachträglichen Aufruf einer der vorhandenen init-Methoden realisiert.Diese Trennung ist wichtig, um Default-Objekte seitens einer korrespondierenden Typklasseerzeugen zu können.

Nichtsdestotrotz steht es dem Entwickler frei zusätzliche "comfort constructors" zu definieren, dieihm letztendlich eine Zeile Code sparen, indem Konstruktion und Initialisierung zusammengefasstsind. Eine kleine Laufzeit- und Speicherbedarfs-Optimierung kann sich dadurch zusätzlichergeben. Aber wie der Name schon sagt, dienen sie hauptsächlich dem Komfort während desProgrammierens. Ihre Erstellung ist nicht zwanghaft.

// --------------------------------------------------// comfort constructors// --------------------------------------------------

/*** Constructs a new Array1 object with elements of type elemType

* (i.e., elements of class corresponding to elemType) and * index set 0 .. n-1. */ public Array1(Type elemType, int n) { this.elemType = elemType; init(n); }

/*** Constructs a new Array1 object with empty domain and element type == T_Object.* Use one of the init methods to get it initialized to your demands.*/public Array1()

{ this(T_Object.type()); }

Benutzerhandbuch

45

Der erste dieser beiden Konstruktoren legt wiederum einen Elementtyp fest, aber diesmal mitdefiniertem Index-Wertebereich. Der zweite Konstruktor zeigt, was passiert, wenn keinElementtyp festgelegt wird. Er setzt automatisch die Typklasse T_Object ein, wodurch dieStandard-Spezialisierung gegeben ist, wie sie in allen allgemeinen generischen Containerklassenvorkommt.

Zur Erstellung eigener generischer Klassen sollte genau nach diesem Schema verfahren werden. AlsStartpunkt ist die Kopie einer ähnlichen generischen Klasse der GGL zu empfehlen, die dann nurnoch an die eigenen Bedürfnisse angepasst werden muss - so ist man auf der sicheren Seite.

Die eben schon erwähnten Initialisierungsmethoden folgen in der Klassendefinition direkt denKonstruktoren. Wie eben schon erwähnt, sind zur besseren allgemeinen Verwendbarkeit, alleInitialisierungsvorgänge einer generischen Klasse von dem Standard-Konstruktor losgelöst. DerStandard-Konstruktor initialisiert nur für das sogenannte Default-Element. Bei Zahlen ist das dieNull in dem jeweiligen Format. Bei Containerklassen ist es meist der leere Container.

/*** Initializes myself with index set 0 .. n-1 (== init(0, n-1)).* Can be used to reinit/reset myself.

* Precondition: n >= 0 (0 => empty index set). * Same as init(0, n-1). */ public void init(int n) { init(0, n-1);

}

Diese ist sehr wichtig, um es noch einmal zu sagen, damit die Methode createDefault in derkorrespondierenden Typklasse ein Default-Element von diesem Array ohne zusätzliche Parameterbis auf den Elementtyp erzeugen kann. Es ist also eine Ausrichtung, die darauf abzielt, für diesesgenerische Array auch eine Typklasse erzeugen zu können, um beliebige Schachtelungstiefen zuerreichen.

Die darauf folgenden Methodendefinitionen basieren auf dem gespeicherten Elementtyp, soferndieser relevant ist. Beispielsweise wird dieser nicht benötigt, um die Größe des Arrays zurückzuge-ben, aber sehr wohl, wenn Kopier-, Sortier- oder Ein- und Ausgabe-Operationen stattfinden.Ähnlich dem Beispiel mit der Zahlenliste in Abschnitt 3.4.6 wird dieser dann verwendet. Ein Blickin die Datei array1.java gibt sicher weitere Aufschlüsse und sollte riskiert werden.

Auch sei der Leser ermutigt sich die zu Array1 korrespondierende Typklasse T_Array1 genaueranzuschauen. Sie wird benötigt, um die Klasse Array1 wiederum als Subdatentyp in anderenContainerklassen einzusetzen.

Von Interesse sollte auch die Klasse Array1_Number sein, die sich von Array1 ableitet und speziellfür Zahleneinträge gemacht worden ist. Sie lässt sich nur mit Typklassen, die von T_Numberabgeleitet sind, spezialisieren, was impliziert, dass die Einträge von der Klasse Number abstammenmüssen. Mit dieser Spezialisierung enthält sie zusätzliche Methoden, die sich auf numerischeOperationen beziehen.

Benutzerhandbuch

46

3.4.8 Die Spezialisierung von Klassen

Um generische Klassen verwenden zu können, müssen sie spezialisiert werden. Es gibt zweigrundlegende Arten der Spezialisierung. Die statische und die dynamische Spezialisierung.

Statische Spezialisierung

Bei der statischen Spezialisierung wird eine neue Klasse entworfen, die sich von der generischenableitet und den oder die Subtypen der Superklasse statisch festlegt oder zumindest einschränkt.Beispiele hierfür sind die Klassen ggl.basic.Array1_Number und ggl.inst.Array_Int. InArray1_Number wird die Klasse des Subdatentyps für die Elemente des Arrays auf abgeleitete derKlasse Number eingeschränkt und in Array1_Int wird diese Klasse auf Integer fixiert.

Die Spezialisierung funktioniert so, dass die Konstruktoren der neuen Klassen die Konstruktorender Superklasse mit den enger gefassten oder fixierten Typklassen aufrufen. Als Beispiel derKonstruktor der Klasse Array1_Int:

public Array1_Int() { super(T_Integer.type()); }

Da durch die statische Spezialisierung eine neue Klasse entsteht, wird sie meist zusätzlich mit densogenannten "comfort methods and constructors" ausgestattet, um ein paar Zeilen Code zubündeln. Zusätzlich wird sie oft auch um weitere Methoden erweitert, um zusätzliche Funktiona-lität, spezifisch für die enger gefassten oder fixierten Subtypen, zu enthalten.

Mit diesen statisch spezialisierten generischen Klassen wird auch eine Typüberprüfung inArgumentlisten realisiert, wie dies beispielsweise in der Methode sumElements(List2_Number x)aus dem Beispiel in Abschnitt 3.4.6 passiert. Andernfalls (sumElements(List2 x)) müsste über dieDokumentation der Methode eingeschränkt werden, welche Subdatentypen sie erlaubt.

Beispiele für statisch spezialisierte generische Klassen finden sich zuhauf in der Package ggl.instund sollten inspiziert werden. Einen Überblick gibt der Anhang A7.

Dynamische Spezialisierung

Bei der dynamischen Spezialisierung wird die Spezialisierung auf Subdatentypen erst dannvorgenommen, wenn ein Objekt der jeweiligen generischen Klasse erzeugt wird. Hierbei wird demKonstruktor die gewünschte Typklasse als Argument übergeben. Es kann hierbei keine neueFunktionalität wie bei der statischen Spezialisierung implementiert werden. Beispiel:

new Array1(T_Integer.type())

Der Vorteil liegt darin, dass keine neue Klasse erzeugt werden muss und diese Spezialisierungdamit sehr schlank bzgl. benötigter Code-Menge und Speicheranforderung ist. Vor allem liegtdieser aber darin, dass, abhängig von anderen Typklassen, Spezialisierungen während der Laufzeitstattfinden können, wodurch ein Höchstmaß an Flexibilität in dem Umgang mit den generischenKlassen gegeben wird. Folgendes Beispiel soll dies verdeutlichen:

Benutzerhandbuch

47

List2 transformArray2List(Array1 arr){Type arrType = arr.getElementType;List2 list = new List2(arrType);

..<transform>..

return list;}

Hier wird ein Array der Klasse Array1 in eine Liste der Klasse List2 transformiert, wobei derElementtyp des Arrays für die erzeugte Liste automatisch übernommen wird. Es muss also keinepassende Liste vorweg erzeugt und als Argument übergeben werden. Die Mächtigkeit derdynamischen Spezialisierung wird dem Benutzer sicher erst mit der Zeit bewusst. Mit ihr kannsehr elegant und effizient programmiert werden.

Implizite Spezialisierung

Wie bereits im Kapitel 3.4.7 erwähnt, existiert in den generischen Basis-Containerklassen bereitsein "comfort constructor", der ohne Typparameter auskommt und die Subdatentypen mittels derTypklasse T_Object für Einträge der Klasse Object spezifiziert. Für diesen allgemeinsten Gebrauchist es also nicht erforderlich die Typklasse T_Object ständig anzugeben. Man kann sagen, dass diegenerischen Containerklassen damit "by default" für Object spezialisiert sind und sich damitähnlich verhalten, wie die bekannten Containerklassen des JDK. Beispiele hierfür sind newArray1(), new List2() aber auch new Graph().

Konkretisierung

Weiterhin wird differenziert zwischen konkretisierten und semi-konkretisierten Spezialisierungen,auch Konkretisierungen und Semi-Konkretisierungen genannt. Bei einer Konkretisierung sind alleSubtypen auf terminale Datentypen fixiert. Diese Spezialisierung muss immer vorgenommenwerden, um ein Objekt einer generischen Klasse zu erzeugen. Beispiele hierzu sind new Array1_Int(statisch) und new Array1(T_Integer.type()) (dynamisch).

Bei einer nur statisch möglichen Semi-Konkretisierung ist mindestens einer der Subtypen nichtterminal, d. h., der Raum für die einsetzbaren Subtypen wird eingeschränkt, die Klasse ist aberimmer noch generisch. Beispiele hierfür sind die Klassen ggl.basic.Array1_Number undggl.inst.Array2_List2. Array1_Number ist nur für Zahlendatentypen (abgeleitet von Number)bestimmt und Array2_List2 für Einträge der wiederum generischen Klasse List2. Um diese zubenutzen, müssen sie vorerst (implizit oder explizit, dynamisch oder statisch) konkretisiert werden.

Mehrdimensionale Spezialisierung

Da für die generischen Datentypen wiederum Typklassen existieren bzw. erstellt werden können,können diese wiederum als Subdatentypen in anderen generischen Klassen eingesetzt werden.Nach diesem Baukastenprinzip kann man beliebig komplexe Strukturen mit wenigen Handgriffenerzeugen. Ein Beispiel mit der dynamischen Spezialisierung:

Benutzerhandbuch

48

new Array1(new T_Array1(new T_Array1(T_List2.type())));

Hier wird ein dreidimensionales Array erzeugt, dessen Elemente wiederum Listen sind. Die Listensind für den Elementtyp Object spezialisiert. Dies ist die Default-Spezialisierung der TypklasseT_List, die man über die Methode type() erhält. Man hätte aber auch

new Array1(new T_Array1(new T_Array1(new T_List2(T_Object.type()))));

schreiben können. Der Unterschied liegt hierbei nur darin, das ein neues Typklassenobjekt für diefür Object spezialisierte Liste erzeugt wird. Dies ist aber unnötig, da bereits eine Instanz in derTypklasse selbst existiert. Siehe zu diesem Thema auch Abschnitt 3.4.4 und 3.5.2.

3.5 Datentypen und AlgorithmenDies ist nicht der Ort, die in der GGL enthaltenen Datentypen und Algorithmen vollständig zubeschreiben, da es des Lesers Fokus von den wichtigen, nicht im API-Guide [Win03] behandeltenThemen, abwenden würde. Falls der Leser anderes erwartet hat, muss ich ihn enttäuschen undnochmals auf den API-Guide und den Anhang A "Die Klassen der GGL" verweisen.

Eine kleine Kurzvorstellung bzw. ein paar ergänzende allgemeine Sätze zu den Basisdatentypen, derKlasse Graph und den Graph-Algorithmen sei hier trotzdem gesagt, um dem Benutzer den Einstiegzu beschleunigen und ein paar Tipps zu geben.

3.5.1 Basisdatentypen

Die Fülle an Kombinationsmöglichkeiten ist schier unendlich. Mit den generischen Basisdaten-typen lassen sich beliebig komplexe Strukturen im Handumdrehen aufbauen. Die generischenDatentypen sind meist mit Ein-/Ausgabemethoden und Ähnlichem ausgestattet, ohne aufMehrdimensionalität (siehe Kapitel 3.4.8) verzichten zu müssen. Das heißt beispielsweise, dasskein Scanner und kein Parser geschrieben werden muss, um eine Ein- und Ausgabe im Textformatzu erreichen (siehe Kapitel 3.3.3). Ebenso sind Deep-Copies von Haus aus eingebaut, aber nichtBedingung. Gleiches gilt auch für das Vergleichen, wobei Untermengen miteinbezogen werden.

Zu bemerken seien auf jeden Fall auch die heterogenen generischen Klassen TwoTuple,ThreeTuple, FourTuple, FiveTuple und NTuple. Heterogen heißt hierbei, dass bei ihnen für jedenEintrag ein eigener Datentyp möglich ist. Das n-Tupel ist damit das Non-Plus-Ultra, wenn manschnell einen beliebigen Record aufbauen möchte. Auch nicht zu vergessen sind die konkretenKlassen DoubleVector und DoubleMatrix, die sehr gut ausgebaut sind und sich damit sehr gut fürAnwendungen der Linearen Algebra eignen.

Benutzerhandbuch

49

3.5.2 Die Klasse Graph

Die Klasse Graph ist das Herzstück der GGL, sie repräsentiert die Datenstruktur Graph, die inerster Linie aus einer Menge von Knoten und Kanten besteht, wobei eine Kante durch zweiKnoten bestimmt ist, den Anfangsknoten und den Endknoten der Kante (gerichteter Graph) oderzwei Endpunkten (ungerichteter Graph). Dies ist sozusagen die Ursuppe, aus der ein Graphbesteht.

Die Klasse Graph enthält auf dieser Basis aber noch weit mehr Struktur und Inhalt. So ist es fürbestimmte Graphengebilde außerdem wichtig, in welcher Reihenfolge die ausgehenden odereintreffenden Kanten geordnet sind, so dass beispielsweise durch Kantenzüge eingegrenzte Gebietedefiniert werden können. Auf dieser Basis lassen sich dann beispielsweise Körper oder dieAufteilung einer Fläche definieren. Ebenso kann ein Graph für Netzwerkprobleme eingesetztwerden, z. B. um eine optimale Ressourcenverteilung zu berechnen oder die kürzesten Wege füreinen Handelsvertreter. Diese Liste an Beispielen ließe sich endlos fortsetzen, da mittels derAssoziation der Knoten und Kanten zu anderen Objekten jegliches Beziehungsgeflecht modelliertwerden kann, ob es der Geometrie oder anderen Fachbereichen entstammt. Kurzum, der DatentypGraph, so wie er in der Klasse Graph implementiert ist, ist sehr vielseitig einsetzbar.

Wie eben und im vorherigen Abschnitt schon angedeutet, können die Knoten, Kanten undoptional, sofern der Graph über bestimmte Eigenschaften verfügt, auch zuvor berechnete Gebietemit zusätzlichen Datentypen verknüpft werden. Der Graph ist somit auch eine Containerklasse,kann also neben seiner Struktur auch noch jegliche andere Objekte enthalten. Die Klasse Graph istgenerisch aufgebaut, d. h., die Effizienz mit ihr zu programmieren ist enorm. Das nun folgendeBeispiel, bei der ein Graph erzeugt wird, dessen Knoten wiederum Graphen mit Double-Einträgenfür Knoten und Kanten und dessen Kanten Listen mit Integer-Einträgen enthalten, zeigt, wasdamit gemeint ist.

// Example 3: Multidimensional Graph

import java.lang.*;import java.util.*;import java.io.IOException;import ggl.basic.List2;import ggl.graph.*;import ggl.type.*;

public final class Example03 { public static void main(String[] args) throws IOException {

// 1. Create a multidimensional directed graph: // Node entries will be subgraphs with Double/Double entries // for nodes/edges. // Edge entries will be lists for Integer entries.

Graph g = new Graph(new T_Graph(T_Double.type(), T_Double.type()), new T_List2(T_Integer.type()));

// 2. Create a subgraphs for initilization

Benutzerhandbuch

50

Graph subG = new Graph(T_Double.type(), T_Double.type()); Node v1 = subG.newNode(new Double(1)); Node v2 = subG.newNode(new Double(2)); Node v3 = subG.newNode(new Double(3));

subG.newEdge(v1, v2, new Double(4));subG.newEdge(v2, v3, new Double(5));

// 3. Create a sub list for initilization List2 subList = new List2(T_Integer.type()); subList.pushBack(new Integer(3)); subList.pushBack(new Integer(4)); subList.pushBack(new Integer(5));

// 4. Fill the graph with nodes and edgesv1 = g.newNode(subG);

v2 = g.newNode(subG); Edge e1 = g.newEdge(v1, v2, subList);

// 5. Clear subG and subList to show deep copy subG.clear(); subList.clear();

// 6. Show node entries for(Enumeration it = g.nodesEnum(); it.hasMoreElements();) { Node v = (Node)it.nextElement(); System.out.println("Now Node " + v.getIndex()); System.out.println(g.inf(v)); }

// 7. Show edge entries for(Enumeration it = g.edgesEnum(); it.hasMoreElements();) { Edge e = (Edge)it.nextElement(); System.out.println("Now Edge " + e.getIndex()); System.out.println(g.inf(e)); }

// 8. Show complete System.out.println(); System.out.println("-- complete --"); System.out.println(); System.out.println(g); }

};

/*-------------------------------------Output:---------------------------------------Now Node 0[0](1.0) : [0]--(4.0)-->[1][1](2.0) : [1]--(5.0)-->[2]

Benutzerhandbuch

51

[2](3.0) :

Now Node 1[0](1.0) : [0]--(4.0)-->[1][1](2.0) : [1]--(5.0)-->[2][2](3.0) :

Now Edge 03 4 5

-- complete --

[0]([0](1.0) : [0]--(4.0)-->[1][1](2.0) : [1]--(5.0)-->[2][2](3.0) :

) : [0]--(3 4 5)-->[1][1]([0](1.0) : [0]--(4.0)-->[1][1](2.0) : [1]--(5.0)-->[2][2](3.0) :

) :

---------------------------------------*/

Zuerst wird der Graph entsprechend erzeugt, in diesem Fall wieder dynamisch spezialisiert (siehevorherigen Abschnitt) für die Art der Einträge (1). Daraufhin wird ein Subgraph erzeugt und mitein paar Einträgen versehen, mit dem der Graph gefüllt werden soll (2). Für die Kanten wird dasGleiche getan, indem eine Liste mit Integer-Einträgen erzeugt wird (3). Dem Graphen werdendaraufhin zwei Knoten zugefügt, mit einer automatischen Kopie des soeben erzeugten Subgraphen,und eine Kante, mit einer automatischen Kopie der erzeugten Subliste (4).

Um zu zeigen, dass es sich hier wirklich um Deep-Copies handelt, d. h., vollständige Kopien undnicht nur Kopien des Referenzwertes, werden der Subgraph und die Subliste anschließend gelöscht(5). Daraufhin werden die Knoteneinträge (6) und die Kanteneinträge (7) zur Kontrolle separatausgegeben. Als Abschluss wird danach der komplette Graph mittels seiner toString-Methodegedruckt, was allerdings nicht so leserlich wie die vorherige Variante ist, durch die gegebeneVerschachtelung.

Die Deep-Copy ist ein wichtiges Hilfsmittel, das hier zur Verfügung steht, ohne die Shallow-Copy, das ist die Kopie bei der nur die Objekt-Referenzen kopiert werden, auszuschließen.

Beispielsweise ist es möglich, den im oberen Beispiel gewonnenen Graph, mit nur zwei Codezeilenvollständig über eine Deep-Copy zu kopieren, ohne extra eine aufwendige Funktion schreiben zumüssen:

Graph g2 = new Graph(new T_Graph(T_Double.type(), T_Double.type()),new T_List2(T_Integer.type()));

g2.init(g);

Benutzerhandbuch

52

Ebenso einfach erzeugt man nur für gewisse Bereiche eine Shallow-Copy, indem man diegenerische Klasse an gewünschter Stelle mit T_Objekt spezialisiert. Zum Beispiel wird durch

Graph g2 = new Graph(T_Object.type(), new T_List2(T_Integer.type()));g2.init(g);

eine Kopie des Graphen g erzeugt, bei der für die Knoteneinträge eine Deep-Copy und für dieKanteneinträge eine Shallow-Copy stattfindet.

3.5.3 Graph-Algorithmen

Die GGL enthält das vollständige Set an Graph-Algorithmen, wie sie in LEDA vorkommen. Siesind alle im GGL API Guide [Win03] so oder noch vollständiger dokumentiert als im LEDA-Manual [LUM99]. Beispiele zu ihrer Verwendung finden sich zudem zuhauf in den in derPackage ggl.test enthaltenen Testprogrammen.

Im LEDA Guide [LDG03] sind zudem sehr gute Beispiele für die Anwendung der Graph-Algorithmen zu finden. Dieser ist auf jeden Fall zu empfehlen. Aufgrund der angewandtenNaming-Conventions heißen die Graph-Algorithmen zwar etwas anders als in der GGL, sind aberleicht aufzufinden.

3.6 IteratorenUm die Produktivität des Programmierers zu erhöhen, bieten alle Containerklassen der GGLIteratoren oder vergleichbare Mittel an, mit denen über die Elemente iteriert werden kann, d. h.,die Elemente Schritt für Schritt in einer definierten Reihenfolge durchlaufen werden können, umOperationen mit oder auf ihnen auszuführen.

Implementiert wurden diese Iteratoren stets über das JDK-Interface java.util.Enumeration. Fürdie Containerklassen, für die keine Iteratoren in dieser Form angeboten werden, existiert eindirekter Zugriff auf die Elemente, meist mit einer Komplexität von O(1). Dies ist beispielsweiseder Fall bei DoubleVector, DoubleMatrix, Array1, Array2, NodeMap, EdgeMap, FaceMap undNodeMatrix (zur Klassenbeschreibung siehe Anhang A). Bei den erstgenannten benutzt man zurIteration einfach eine normale Schleife mit einer Zählervariablen, die sich im gültigen Definitions-bereich, der als Abbildung betrachteten Containerklasse, aufhält. Die zuletzt genannten betreffendie Strukturelemente der Klasse Graph. Bei ihnen greift man über die Graph-Elemente Node, Edgeund Face auf die abgebildeten Werte zu. Will man diese in ihrer Gesamtheit durchlaufen und hatvorher keine Referenzen gespeichert, dann bietet die Klasse Graph Enumerations in vielfältigerForm an, mit denen man einen Durchlauf organisieren kann.

Die Iteratoren der Containerklassen der GGL (die Klasse Graph gehört ebenfalls dazu) basieren alleauf dem Java Standardinterface java.util.Enumeration und sind damit abwärtskompatibel bisJDK 1.0. Der Nachfolger steht allerdings bereits fest, heißt java.util.Iterator und ist seit JDK1.2 eingeführt. Er erlaubt in eingeschränkter Art und Weise auch das Löschen von Elementen desContainers während der Iteration und ist daher für Filteroperationen sehr gut geeignet.

Benutzerhandbuch

53

Die im vorherigen Kapitel kennengelernte Klasse Graph wird im jetzt folgenden Abschnitt alsBeispiel für Iteration genutzt. Sie ist die komplexeste Datenstruktur der GGL und enthält daherauch die größte Anzahl an Iteratoren. Darauf folgt der Abschnitt "Sichere Iteration", der vierkleine Adapter vorstellt, mit denen eine Iteration insofern gesichert ausgeführt werden kann, dasswährend der Iteration auch Elemente der iterierten Menge aus dem Container gelöscht oderumgeordnet werden können.

3.6.1 Beispiel-Iteration mit Graph

In diesem Beispiel werden die Knoten und Kanten eines kleinen Beispiel-Graphen in verschiede-nen Varianten durchlaufen. Die Klasse Graph wurde gewählt, da sie die wichtigste Datenstrukturder GGL darstellt und so schon ein kleiner Einblick gewährt wird, bevor der Benutzer mithilfe desGGL API Guides [Win03] die Klasse als Ganzes erforscht.

// Example 2: Iteration with a Graph

import java.lang.*;import java.io.IOException;import java.util.Enumeration;import ggl.graph.*;

public final class Test{

public static void main(String[] args) throws IOException{

// 1. Create a directed graph with 3 nodes and 2 edges Graph g = new Graph(); Node v1 = g.newNode(); Node v2 = g.newNode(); Node v3 = g.newNode(); Edge e1 = g.newEdge(v1, v2); Edge e2 = g.newEdge(v2, v3);

// 2. Iterate through the nodes System.out.println("g.nodesEnum()"); for(Enumeration it = g.nodesEnum(); it.hasMoreElements();) { System.out.println("Now Node " + ((Node)it.nextElement()).getIndex()); } System.out.println();

// 3. Iterate through the edges System.out.println("g.edgesEnum()"); for(Enumeration it = g.edgesEnum(); it.hasMoreElements(); ) { System.out.println("Now Edge " + ((Edge)it.nextElement()).getIndex()); }

Benutzerhandbuch

54

System.out.println();

// 4. Iterate through nodes/edges adjacent to node v2 // --------------------------------------------------

// 4a. Iterate through incomming edges of node v2 System.out.println("g.inEdgesEnum(v2)"); for(Enumeration it = g.inEdgesEnum(v2); it.hasMoreElements(); ) { System.out.println("Now In-Edge " + ((Edge)it.nextElement()).getIndex()); }

System.out.println();

// 4b. Iterate through outgoing edges of node v2 System.out.println("g.outEdgesEnum(v2)");

for(Enumeration it = g.outEdgesEnum(v2); it.hasMoreElements(); ) {

System.out.println("Now Out-Edge " + ((Edge)it.nextElement()).getIndex()); }

System.out.println();

// 4c. Iterate through incomming and outgoing edges of node v2System.out.println("g.inOutEdgesEnum(v2)");for(Enumeration it = g.inOutEdgesEnum(v2); it.hasMoreElements(); )

{ System.out.println("Now InOut-Edge " +

((Edge)it.nextElement()).getIndex()); }

System.out.println();

// 4d. Iterate through nodes adjacent to node v2System.out.println("g.adjNodesEnum(v2)");for(Enumeration it = g.adjNodesEnum(v2); it.hasMoreElements(); )

{ System.out.println("Now Adj-Node " + ((Node)it.nextElement()).getIndex()); }

System.out.println(); }

};

/*-------------------------------------Output:---------------------------------------g.nodesEnum()Now Node 0Now Node 1Now Node 2

g.edgesEnum()

Benutzerhandbuch

55

Now Edge 0Now Edge 1

g.inEdgesEnum(v2)Now In-Edge 0

g.outEdgesEnum(v2)Now Out-Edge 1

g.inOutEdgesEnum(v2)Now InOut-Edge 1Now InOut-Edge 0

g.adjNodesEnum(v2)Now Adj-Node 2

---------------------------------------*/

Wie im obersten Teil zu sehen ist (1), wird ein Graph erzeugt und mit 3 Knoten und 2 Kantengefüllt. Zu bemerken ist, dass die Klasse Graph generisch ist, das heißt, die Knoten und Kantenkönnen mit Werten eines beliebigen Datentyps belegt werden, obwohl es in der Konstruktionszeilenicht so aussieht. Fakt ist, dass "by default" als Knoten- und Kantentyp Object über die TypklasseT_Object eingestellt ist, aber das nur am Rande.

Des weiteren wird über den Konstruktor standardmäßig ein gerichteter Graph erzeugt. Für einenungerichteten Graphen müsste noch eine Codezeile hinzugefügt werden, die ihn entsprechendverwandelt - aber jetzt soll das Augenmerk voll und ganz den Iteratoren dienen.

Der Beispielgraph wurde gewählt, weil er wirklich leicht gedanklich zu fassen ist. Zuerst wird überalle Knoten (2) und dann über alle Kanten (3) des Graphen iteriert. Daraufhin wird der Knoten v2ausgewählt und über alle angrenzenden Knoten und Kanten iteriert (4), wobei hierbei differenziertwird, ob es sich um eingehende (4a), abgehende (4b), eingehende und abgehende Kanten (4c) odererreichbare Knoten (4d) handelt. Im letzteren Fall muss bei einem gerichteten Graphen für denKnoten eine abgehende Kante existieren. Für die Klasse Graph existieren insgesamt 15 verschiedeneIteratoren auf Basis von java.util.Enumeration.

3.6.2 Sichere Iteratoren

Die GGL bietet als Ausgleich zu dem nicht eingesetzten Java-Interface java.util.Iterator vierHilfsmethoden aus der Klasse ggl.util.Misc an. Diese heißen enum2List, enum2ListRev,getBufferedEnum und getBufferedEnumRev. Sie erlauben eine Enumeration zu einer Liste zutransformieren oder in eine gepufferte Enumeration zu verwandeln, die jegliche Löschoperationenoder anderweitige Operationen auf der Containerklasse erlauben, ohne dabei die ursprünglicheIteration über die Elemente zu stören.

Wird eine Enumeration in eine Liste der Klasse List2 verwandelt, so stehen zudem effizienteSortier- und Suchalgorithmen zur Verfügung. Für die Detail-Beschreibungen der Iteratoren-Hilfsmethoden sei wiederum der GGL API Guide [Win03] empfohlen.

Benutzerhandbuch

56

3.7 Das TestsystemDas Testsystem, das in der GGL enthalten ist, wurde für die Entwicklung und Optimierungbenötigt und stetig verfeinert. Es besteht aus den beiden Hauptteilen Test-Framework(ggl.util.Tfw) und Test-Programme (ggl.test.*).

Das Test-Framework bietet Routinen an, mit denen leicht ein eigenes komfortables Testprogrammgeschrieben werden kann. Diese sind alle im API-Guide [Win03] dokumentiert.

Gleiches gilt für die zahlreichen Tests, die ebenfalls sehr ausführlich im API-Guide beschriebensind und auch im Anhang A auf die Schnelle auf ihre Inhalte hin überprüft werden können.

3.8 PerformanceIn diesem Kapitel werden in aller Kürze die Ergebnisse der vielen Benchmarks, die gemessenwurden, vorgestellt. Eigene Benchmarks können leicht (siehe vorherigen Abschnitt) selbst erstelltwerden. Um die Resultate vorwegzunehmen: Die GGL ist schnell. Die erste lauffähigeTransformation (GGL 0.0.1) brachte bereits gute Ergebnisse, die im Vergleich zu LEDA aber umca. Faktor 3 hinter dem Original zurückstanden. In der daran anschließenden Optimierungsphase- wobei "Optimierung" ein etwas zu schwacher Ausdruck für den stattgefundenen Totalumbau ist(siehe Kapitel 2.2) - wurde die Geschwindigkeit um fast ca. 100 % gesteigert.

Die dafür durchgeführten Testläufe wurden unter Mac OS 8.6 ausgeführt und vollzogen sichimmer mit den gleichen, vorher aufgenommenen "Zufallswerten". Das heißt, die Testläufe warenstets deterministisch und erlaubten daher einen wahren Vergleich. Allerdings ist die Ein-/Ausgabebei der Java-VM MRJ 2.1.4 (Java Virtual Machine für Mac OS 8.6-9.2) sehr viel langsamergewesen, als bei der C-Konsole, die sich seitens der Metrowerks-IDE in das LEDA-Projektmiteinbinden ließ. Dadurch wurden einige Ergebnisse getrübt. Auch wurde eine LEDA-Variantebenutzt, bei der die Speicherverwaltung bereits modifiziert war. Es handelte sich also nicht um dieabsolute Originalversion.

Verglichen wurden die Ergebnisse auch mit einem Testlauf unter Mac OS 10.2.2 mit dem JDK1.4.1 auf einem G4-Powerbook mit 667 Mhz. Allerdings reichten 48 Stunden nicht aus, LEDA3.8a dort lauffähig zu installieren. Die GGL-Ergebnisse zeigten auf dieser Plattform, relativ zurProzessorleistung, keine besseren Ergebnisse. Dies muss nicht unbedingt an der integrierten JavaVirtual Machine liegen, die sehr viel weiterentwickelter als die MRJ 2.1.4 ist, sondern kann auchan den vielen kleinen "Schnick-Schnack"-Prozessen im Hintergrund und dem noch zuverbessernden Scheduler liegen.

Benutzerhandbuch

57

Die Testplattform:

Umax Pulsar, G3/400 Mhz, MacOS 8.6,C++- und Java-Compiler jeweils der Metrowerks CodeWarrior 5.0,Java-VM: MRJ 2.1.4

Die Ergebnisse in Kurzform:

LEDA:

GraphRegTest passed : 25.316 sec

GGL vor Optimierung:

GraphRegTest passed : 75.514 sec

GGL nach Optimierung

GraphRegTest passed : 37.933 sec

Wie sollen diese Ergebnisse nun bewertet werden? Da sich auch die Java-VMs ständig weiter-entwickeln, ist es möglich, dass sich die Schere zwischen LEDA und der GGL weiter schließt.

Ich hoffe, dass diese Ergebnisse zumindest ausreichen, den Leser soweit zu überzeugen, dass er derGGL für seine eigenen Projekte eine Chance gibt. Es ist meine vollste Überzeugung, dass ihreVerwendung den Benutzer zumindest zufrieden stellt.

58

Zusammenfassung und Ausblick

59

4 Zusammenfassung undAusblick

Zusammenfassung

Mit der Erstellung der GGL wurde gezeigt,

- dass nahezu jede C++ Bibliothek nach Java transformiert werden kann, da mit LEDA eineBibliothek nach Java transformiert wurde, die kaum ein C++-Sprachelement oder bessergesagt, kaum einen C++-Trick auslässt.

- dass man selbst eine so renommierte Bibliothek wie LEDA noch eleganter gestalten kann.

- dass ein "aufgesetztes" System zur generischen Programmierung in Java Sinn macht und sehrgut funktioniert, vor allem durch die gewonnene dynamische Spezialisierung generischerKlassen.

- dass jetzt eine Graph-Bibliothek für Java für die TU-Braunschweig existiert, die das Potentialhat, ein Erfolg zu werden.

Die Schwachstellen im Vergleich zu LEDA:

1. Das Copyright LEDAs wirkt aufgrund der Art der Entwicklung der GGL nach wie vor, auchwenn "kein Stein auf dem anderen geblieben ist" und weder die Firma Algorithmic SolutionsSoftware GmbH noch das Max-Planck-Institut für die ursprünglichen Algorithmen, wie sie inder Primärliteratur zu finden sind, ein Copyright besitzen. Ein kommerzieller Einsatz der GGList daher in der jetzigen Form ausgeschlossen.

2. Die Unterstützung für Multithreading fehlt, auch wenn die Grundlagen dafür bereitsgeschaffen worden sind. Die Bibliothek müsste zwar dafür nur konsequent mit dem Java-Schlüsselwort 'synchronized' durchdrungen werden, doch ist die Codemenge das großeHindernis (> 2MB).

3. Die Menge an Datentypen und Algorithmen in LEDA ist größer als die der GGL. Es wurdenzwar alle graph- und netzwerkrelevanten Datentypen und Algorithmen transformiert, zuzüglichder in ihnen verwendeten Basisdatentypen und -algorithmen, doch bietet LEDA noch zusätzli-che Module wie z. B. für Geometrie und Visualisierung.

4. Die Performance LEDAs bzgl. der Laufzeit ist höher, da als Zielsprache C++ verwendet wirdund die dafür verfügbaren Compiler echten Maschinencode produzieren. Java basiert auf einerVirtual Machine, die einen Overhead an benötigter Rechenleistung mit sich bringt. Allerdingsgibt es für einige Plattformen bereits Java-Compiler, die ebenfalls echten Maschinencodeproduzieren, oft über den Umweg einer vorherigen automatischen Transformation nach C++.

Zusammenfassung und Ausblick

60

Mit Verwendung der GGL hat man demnach immer noch die Option im Zweifelsfalle einManko bzgl. der Laufzeit auf einer bestimmten Plattform zu beheben.

5. Die Verantwortung für Weiterentwicklung und Wartung der GGL ist nicht determiniert.Für kommerzielle Großkunden wäre dies ein kaum hinzunehmender Sachverhalt, da Investiti-onssicherheit ein wichtiges Kriterium ist - mag die GGL noch so gut sein. Die TU-Braunschweig wird bei Bedarf allerdings sicher auf eigene Ressourcen zurückgreifen können,um eine zentrale Koordinationsstelle für die Weiterentwicklung zu bestimmen.

Die Vorteile der GGL:

1. Die GGL enthält alle graph- und netzwerkrelevanten Datentypen und Algorithmen sowieviele Basisklassen und -algorithmen der LEDA-Bibliothek in der Version 3.8a. Das bedeutet, sieist gefüllt mit sehr viel algorithmischem Know-how und damit ein mächtiges Werkzeug für eineVielzahl graph- und netzwerkbezogener Probleme.

2. Die GGL liefert ein neues Konzept und eine Umgebung für generische Programmierung mitJava. Sie besteht vorzugsweise aus generischen Datentypen und Algorithmen und enthält nebendem graphbezogenem Teil eine Menge an Basisdatentypen und -algorithmen. Sie ist daheräußerst universell und in jedem Java-Softwareprojekt einsetzbar.

3. Die GGL besteht aus reinem, nur auf der Standardbibliothek aufbauendem Java-Code undist damit sofort auf jeder Plattform lauffähig. Sie ist auf Kompatibilität mit JDK 1.1 bis 1.4.1getestet und auf Kompatibilität mit zukünftigen Versionen ausgelegt.

4. Die GGL ist äußerst einfach zu installieren und hat minimale Systemanforderungen. Derbenötigte Speicher hängt, bis auf einen zu vernachlässigbaren kleinen Teil, nur von denProgrammen ab, die sie benutzen.

5. Die GGL wurde bzgl. Laufzeit und Speicheranforderungen hoch optimiert.

6. Die GGL wurde ausgiebig getestet und enthält ein integriertes Testsystem, sowie eineneingebauten Regressions- und Performancetest. Dies erlaubt die Überprüfung auf korrekteFunktionalität sofort nach Installation und erleichtert sehr die zukünftige Weiterentwicklung.

7. Die GGL wurde, im Vergleich zu LEDA, um viele Klassen reduziert, um die Transparenzund Performance zu erhöhen, ohne die Funktionalität einzuschränken, sondern diese sogar zuerweitern. Die Schnittstelle für den Benutzer ist dadurch einfacher und leichter erlernbar.Zudem traten durch den Transformationsprozess mehrere Fehler in LEDA ans Tageslicht, dieselbstverständlich eliminiert wurden.

8. Die GGL ist vollständig dokumentiert, d. h., jede nach außen sichtbare Klasse und Methode,für den Benutzer wie auch für den Entwickler, ist im API-Guide dokumentiert enthalten.

9. Die GGL ist leicht erlernbar, der Quellcode gut strukturiert und kommentiert. Auch durchdie strikte Einhaltung von Java Coding Standards wurde eine durchgängig hohe Codequalitäterreicht, die zum Wiederverwenden einlädt.

Zusammenfassung und Ausblick

61

10. Die GGL ist auf Homogenität getrimmt. Das bedeutet, dass alle Klassenkonstruktoren demgleichen Schema folgen, dass gleiche Methoden verschiedener Datentypen auch gleiche Namentragen, dass die Mengen verfügbarer Operationen angeglichen sind und dass damit dieenthaltenen Algorithmen auf einer maximalen Anzahl von Datentypen ihre Wirkung entfaltenkönnen.

11. Alle Containerklassen enthalten Iteratoren, sofern auf die Inhalte nicht direkt zugegriffenwerden kann, und Methoden für die Ein-/Ausgabe im Textformat. Es gibt auch Adapter fürsichere Iteratoren, die erlauben, die Containerklassen während des Iterationsvorgangs über dieElemente zu modifizieren.

12. Alle Containerklassen sind generisch ausgerichtet. Sie sind beliebig schachtelbar, d. h., einContainer kann als Elemente wiederum Container enthalten etc. Dadurch lassen sich imHandumdrehen komplexe Datenstrukturen statisch wie auch dynamisch erzeugen.

13. Jeder (!) Datentyp kann mit den generischen Klassen und Methoden verwendet werden.Dies schließt die der Java Standardbibliothek JDK genauso mit ein, wie die jeder anderen Java-Bibliothek. Die GGL ist also keine Insellösung, sondern passt sich bestens in andere Java-Umgebungen ein wie auch in bereits begonnene Softwareprojekte.

14. Mit der GGL ist es, trotz der generischen Ausrichtung, leicht zu debuggen. Das generischeKonzept erhöht durch seine homogenen Schnittstellen sogar die Transparenz. Viele C++-Entwickler vermeiden generisches Programmieren in Form von Templates, gerade weil sie denMangel an Transparenz während der Fehlersuche und damit auch die Dauer der Fehlerkorrek-turphase nicht akzeptieren wollen.

15. Das generische Konzept beschleunigt nicht nur die Programmentwicklung, sondern durchintensive Cacheausnutzung auch die Programme selbst. Durch geschickte Programmierungkann der allgemeine Offset generischer im Vergleich zu konkreter Programmierung sogar mehrals wett gemacht werden.

16. Die GGL enthält viele Hilfsmethoden, die die Transformation anderer C++-Programmenach Java erleichtern. Sie enthält selbsttestende Algorithmen, wobei sich der Selbsttest an- undausschalten lässt (Entwicklungsphase/Anwendungsphase).

17. Die GGL ist durch den verwendeten Programmierstil leicht erweiterbar und wartbar.Klassenabhängigkeiten sind auf ein Minimum reduziert. Gemeinsame variable Speicherbereichezwischen Klassen kommen nicht vor. Seiteneffekten wird so vorgebeugt.

18. Die Mittel, die dem Entwickler über die GGL zur Hand gereicht werden, führen zueffizientem, leicht verständlichem Code mit hoher Performance bei geringem Speicherbedarf.

Ausblick

Wenn die Argumente für die GGL ausreichen, einen großen Freundeskreis zu finden, was ichhoffe und annehme, dann ist der Ausblick für die GGL eine stetige Weiterentwicklung und für dieBenutzer eine hohe Produktivitätssteigerung. Die Weiterentwicklung kann durch zukünftige Java-Versionen wie durch den Wunsch nach Multithreading-Unterstützung oder der Ergänzung um

Zusammenfassung und Ausblick

62

zusätzliche Algorithmen und Datenstrukturen bedingt sein. Allerdings nehme ich an, da die GGLhoch optimiert, getestet und vervollständigt ist, dass vorerst niemand ein Update benötigt und sichan der GGL erfreut, wie sie ist.

Im Zuge der Zeit könnten sich auch Ergänzungsbibliotheken entwickeln, beispielsweise eine mitzusätzlichen Typklassen, eine mit zusätzlichen Algorithmen oder auch eine mit zusätzlichengenerischen Datentypen. Die Trennung dieser von der GGL bis zu einer gewissen Reife machtdurchaus Sinn, um die Stabilität des Systems nicht zu gefährden. Danach könnte man sie mit derGGL verschmelzen, obwohl auch das Modulkonzept etwas für sich hat. Das Verändern der GGL1.0 ist nur dann notwendig, wenn in ihr Fehler gefunden werden oder aus anderen Gründen in ihrenthaltene Datentypen und Algorithmen ergänzt bzw. verändert werden sollen.

Literatur

63

5 Literatur

5.1 Literatur zu LEDA, Java und C++

LEDA:

LUM99 The LEDA User Manual 3.8a,Algorithmic Solutions Software GmbH, Saarbrücken, 1999

LUM03 The LEDA User Manual 4.4,Algorithmic Solutions Software GmbH, Saarbrücken, 2003,siehe http://www.algorithmic-solutions.info/leda_manual/

LDG03 The LEDA Guide 1.1,Algorithmic Solutions Software GmbH, Saarbrücken, 2003,siehe http://www.algorithmic-solutions.info/leda_guide/index.html

MN89 K. Mehlhorn, S. Näher: "LEDA, a Library of Efficient Data Typesand Algorithms", TR A 04/89, FB10, Universität des Saarlandes,Saarbrücken, 1989

Java:

AG99 K. Arnold, J. Gosling: "The Java Programming Language,Second Edition", Addison-Wesley, 1999

JDK98 JDK 1.1.8 API-Dokumentation,Sun Microsystems, Inc., 1998,siehe http://java.sun.com/products/jdk/1.1/docs/api/

JGL97 JGL Version 3.1 Documentation,ObjectSpace, Inc., 1997,siehe http://www.recursionsw.com/products/jgl/

Amb97 Scott W. Ambler: "The AmbySoft Inc. Coding Standards for Java",Version 17.01c, AmbySoft, Inc., 1997

C++:

Str91 B. Stroustrup: "The C++ Programming Language, Second Edition",Addison-Wesley Publishing Company, 1991

Literatur

64

GGL:

Win03 C. Winckler: "GGL API Guide",TU-Braunschweig, Abteilung Entwurf Integrierter Schaltungen, 2003,Teil der GGL 1.0 Distribution

Websites:

WMA02 http://www.martinfowler.de, Homepage von Martin Fowler, 2002

5.2 Primärliteratur zu den Graph-Algorithmen

Die Verweise zu dieser Literatur befinden sich in den Beschreibungen zu den implementiertenAlgorithmen in dem GGL API Guide [Win03].

ABMP91 H. Alt, N. Blum, K. Mehlhorn, M. Paul: "Computing a maximumcardinality matching in a bipartite graph in time O(n^1.5 sqrt(m/log n))",

Information Processing Letters, Vol. 37, No. 4, 237-240, 1991

AMO93 R.K. Ahuja, T.L. Magnanti, J.B. Orlin: "Network Flows",Section 10.2., Prentice Hall, 1993

Bel58 R.E. Bellman: "On a Routing Problem",Quart. Appl. Math. 16, 87-90, 1958

CM96 J. Cheriyan, K. Mehlhorn: "Algorithms for Dense Graphs andNetworks on the Random Access Computer",Algorithmica, Vol. 15, No. 6, 521-549, 1996

Edm65 J. Edmonds: "Paths, Trees, and Flowers",Canad. J. Math., Vol. 17, 449-467, 1965

EK72 J. Edmonds, R.M. Karp: "Theoretical Improvements in AlgorithmicEfficiency for Network Flow Problems",Journal of the ACM, Vol. 19, No. 2, 1972

Far48 I. Fary: "On Straight Line Representing of Planar Graphs",Acta. Sci. Math. Vol. 11, 229-233, 1948

FF63 L.R. Ford, D.R. Fulkerson: "Flows in Networks",Princeton Univ. Press, 1963

FT87 M.L. Fredman, R.E. Tarjan: "Fibonacci Heaps and Their Uses inImproved Network Optimization Algorithms",Journal of the ACM, Vol. 34, 596-615, 1987

Literatur

65

Gab74 H. N. Gabow: "Implementation of algorithms for maximum matchingon nonbipartite graphs", Ph.D. thesis, Stanford Univ.,Stanford, CA, 1974

Gab76 H.N.Gabow: "An efficient implementation of Edmond's algorithm formaximum matching on graphs. Journal of the ACM",Vol. 23, 221-234, 1976

GK79 A. Goralcikova, V. Konbek: "A Reduct and Closure Algorithm forGraphs", Mathematical Foundations of Computer Science,LNCS 74, 301-307, 1979

GT88 Goldberg, R.E. Tarjan: "A New Approach to the Maximum FlowProblem", Journal of the ACM, Vol. 35, 921-940, 1988

Him97 M. Himsolt: "GML: A portable Graph File Format",Technical Report, Universität Passau, 1997,siehe http://www.fmi.uni-passau.de/himsolt/Graphlet/GML

HK75 J.E. Hopcroft, R.M. Karp: "An O(n2.5) Algorithm for Matching inBipartite Graphs", SIAM Journal of Computing, Vol. 4, 225-231, 1975

HT74 J.E. Hopcroft, R.E. Tarjan: "Efficient Planarity Testing",Journal of the ACM, Vol. 21, 549-568, 1974

HU89 T. Hagerup, C. Uhrig: "Triangulating a Planar Map WithoutIntroducing multiple Arcs", unpublished, 1989

Kah62 A.B. Kahn: "Topological Sorting of Large Networks",Communications of the ACM, Vol. 5, 558-562, 1962

Kru56 J.B. Kruskal: "On the Shortest Spanning Subtree of a Graph and theTravelling Salesman Problem",Proc. American Math. Society 7, 48-50, 1956

Law76 E.L. Lawler: "Combinatorial Optimization: Networks and Matroids",Holt, Rinehart and Winston, New York, 1976

Meh84 K. Mehlhorn: "Data Structures and Algorithms",Vol. 1-3, Springer Publishing Company, 1984

Pug90 W. Pugh: "Skip Lists: A Probabilistic Alternative to Balanced Trees",Communications of the ACM, Vol. 33, No. 6, 668-676, 1990

SW94 M. Stoer and F. Wagner: "A Simple Min Cut Algorithm",Algorithms-ESA '94, LNCS 855, 141- 147, 1994

Tar72 R.E. Tarjan: "Depth First Search and Linear Graph Algorithms",SIAM Journal of Computing, Vol. 1, 146-160, 1972

Whi73 A.G. White: "Graphs,Groups, and Surfaces",North Holland, 1973

66

Anhang AKlassen der GGL

67

Anhang AKlassen der GGL

Dieser Anhang enthält eine Zusammenfassung aller Klassen der GGL, geordnet nach Packages. Inden nun folgenden Tabellen sind die Klassen nach ihrer Art bzw. ihren wesentlichen Merkmalengruppiert. Zur leichteren Lesbarkeit sind die Gruppen durch etwas stärkere Trennlinien zwischenden Klassen grafisch etwas voneinander abgesetzt.

A.1 Package ggl.basic

Diese Package enthält hauptsächlich die generischen Standarddatentypen der GGL, die Container-klassen für Listen, Arrays, Heaps, Trees, Dictionaries und Priority Queues - aber auch zweinumerische Klassen, prädestiniert für Lineare Algebra. Sie stellt das Rüstzeug der GGL dar und istvon den Graph-Packages (ggl.graph, ggl.graphIO, ggl.graphAlg) vollkommen unabhängig.

Globals Definitionen einzelner globaler Konstanten

IllegalOperationException Runtime-Exception für illegal ausgeführte Operationen

Compare Interface für Vergleichsoperationen (lineare Ordnung)

Ordering Interface für Ordnungsfunktion (Bucketsort)

TwoTuple Generisches heterogenes 2-Tupel

ThreeTuple Generisches heterogenes 3-Tupel

FourTuple Generisches heterogenes 4-Tupel

FiveTuple Generisches heterogenes 5-Tupel

NTuple Generisches heterogenes n-Tupel (beliebig viele Elemente)

Array1 Generisches 1-dim. Feld

Array1_Number Generisches 1-dim. Feld speziell für Zahlentypen

Array2 Generisches 2-dim. Feld

Array2_Number Generisches 2-dim. Feld speziell für Zahlentypen

List2 Generische doppelt verkettete Liste

List2_Number Generische doppelt verkettete Liste speziell für Zahlentypen

Anhang AKlassen der GGL

68

FHeap Generischer Fibonachi Heap

BinHeap Generischer Binary Heap

PQueue Generische Priority Queue

SkipList Generische Skip List

SortSeq Generische Sorted Sequence

AbTree Generischer a-b-Tree

Dictionary Generisches Wörterbuch

DoubleVector Vektor mit Double-Einträgen (für Lineare Algebra)

DoubleMatrix Matrix mit Double-Einträgen, (für Lineare Algebra)

A.2 Package ggl.graph

Die Package ggl.graph enthält die Klasse Graph und verwandte Datentypen wie NodeMap,NodeMatrix, NodeSet, etc. Zwei Ausnahmen machen die beiden Klassen GraphGen und GraphMisc.GraphGen enthält Graph-Generatoren die sehr hilfreich sind, vor allem um Testprogramme zuschreiben. Und GraphMisc ist ein Verbund vieler kleiner Hilfsalgorithmen für die Klasse Graph.

Graph Der universelle generische Graph

Node Knoten eines Graphen

Edge Kante eines Graphen

Face Spezieller geschlossener Kantenzug eines Graphen / Gebiet

GraphItemMap Basisklasse für NodeMap, EdgeMap und FaceMap

NodeMap Generische Abbildung der Knoten eines Graphen

NodeMap_Number Spezielle NodeMap für Zahlentypen

EdgeMap Generische Abbildung der Kanten eines Graphen

EdgeMap_Number Spezielle EdgeMap für Zahlentypen

FaceMap Generische Abbildung der Faces eines Graphen

FaceMap_Number Spezielle FaceMap für Zahlentypen

NodeMap2Generische 2-dim. Abb. der Knoten eines Graphen,Variante 1

NodeMap2_Number Spezielle NodeMap2 für Zahlentypen

NodeMatrixGenerische 2-dim. Abb. der Knoten eines Graphen,Variante 2

Anhang AKlassen der GGL

69

NodeMatrix_Number Spezielle NodeMatrix für Zahlentypen

NodePartition Partitionierung der Knoten eines Graphen

NodeSet Teilmenge der Knoten eines Graphen

NodePq Generische Prioritätsschlange für Knoten eines Graphen

NodePq_Number Spezielle NodePq für Zahlentypen

GraphGen Verschiedene Graphgeneratoren

GraphMisc Verschiedene „kleine“ Graphoperationen

A.3 Package ggl.graphAlg

Diese Package enthält alle speziellen Graph-Algorithmen der GGL, untergebracht in verschiedenenKlassen nach Kategorie. Auf der Suche nach einem Algorithmus ist die Klasse Graph ebenfallsmiteinzubeziehen, da in ihr bereits mehrere der Standardalgorithmen aufgenommen sind. DieKlasse ggl.graph.GraphMisc ist ebenfalls zu beachten.

BasicGraphAlg Standard-Graph-Algorithmen wie DFS, BFS, topsort, etc.

GraphDraw Algorithmen zum Zeichnen eines Graphen

MaxCardMatch Maximum Cardinality Matchings in General Graphs

MaxCardMatchBi Maximum Cardinality Matchings in Bipartite Graphs

MaxFlow Maximum Flow-Algorithmen

MaxWeightMatch Maximum Weight Matchings in General Graphs

MaxWeightMatchBi Min/Maximum Weight Matchings for Bipartite Graphs

MinCostFlow Min Cost Flow-Algorithmen

MinCut Minimum Cut-Algorithmus

MinSpan Minimum Spanning Tree-Algorithmen

PlaneGraphAlg Algorithmen für planare Graphen

ShortestPath Shortest Path-Algorithmen

Anhang AKlassen der GGL

70

A.4 Package ggl.graphIO

Dies ist die Graph-Ein-/Ausgabe-Package, die aber genau genommen nur aus dem GML-Parser,also den Routinen zum Einlesen eines Graphen in GML-Format besteht. Die Ausgabe-Methodenfür das GGL- und das GML-Format, sowie die Einleseroutinen für das GGL-Format sind in derKlasse Graph implementiert.

GmlGraph Parser für Graphen in GML-Format

GmlParser Basisparser und –klasse für GmlGraph

GmlObjectTree Hilfsklasse für den Ableitungsbaum in GmlParser

GmlObject Container für eine GML-Informationseinheit

GmlParseErrorException Java-Runtime-Exception für einen GML-Parsefehler

GmlSyntaxErrorException Java-Runtime-Exception für einen GML-Syntaxfehler

A.5 Package ggl.util

Dies ist die Package mit den Werkzeugklassen. Die Klassen GglMath, InOut und Misc zeichnen sichjeweils durch die Menge der in ihnen enthaltenen kleinen Helfer aus. Die Klassen Debug und Tfwsind entwicklungsbedingt entstanden und dienen dem Testen der GGL und der Fehlersuche.

Debug Hilfsklasse für die Fehlersuche

GglMath Verschiedene mathematische Routinen

InOut Verschiedene Ein-/Ausgaberoutinen

Misc Sonstige kleine Helfer

Tfw Testframework der GGL (Operationen zum Testen)

Anhang AKlassen der GGL

71

A.6 Package ggl.type

Diese Package enthält alle Typklassen. Für die Gruppierung in unten aufgeführter Tabelle ist derKlassenableitungsbaum und die Package der korrespondierenden Klassen verantwortlich.Begonnen wird mit den Typen, die sich auf Java-Standardklassen beziehen und fortgesetzt mit denPackages ggl.basic und ggl.graph. Typen zu Klassen der Package ggl.inst sind entsprechendder Klassenableitung miteingewebt. Für weitere Informationen zu Typklassen siehe Kapitel 3.4„Generisches Programmieren mit Type“.

TypeBasisklasse für alle Typdefinitionen zur Spezialisierunggenerischer Klassen und Methoden

T_ObjectTypdefinition für die Klasse java.lang.Object /Defaulttyp der meisten generischen Klassen

T_Boolean Typdefinition für die Klasse java.lang.Boolean

T_Character Typdefinition für die Klasse java.lang.Character

T_String Typdefinition für die Klasse java.lang.String

T_NumberBasisklasse für alle Zahlentypen /Typdefinition für alle Klasse java.lang.Number

T_Integer Typdefinition für die Klasse java.lang.Integer

T_Long Typdefinition für die Klasse java.lang.Long

T_Float Typdefinition für die Klasse java.lang.Float

T_Double Typdefinition für die Klasse java.lang.Double

T_BigInteger Typdefinition für die Klasse java.math.BigInteger

T_TwoTuple Typdefinition für die gen. Klasse TwoTuple

T_ThreeTuple Typdefinition für die gen. Klasse ThreeTuple

T_FourTuple Typdefinition für die gen. Klasse FourTuple

T_FiveTuple Typdefinition für die gen. Klasse FiveTuple

T_NTuple Typdefinition für die gen. Klasse NTuple

T_Array1 Typdefinition für die gen. Klasse Array1

T_Array1_Number Typdefinition für die gen. Klasse Array1_Number

T_Array2 Typdefinition für die gen. Klasse Array2

T_Array2_Number Typdefinition für die gen. Klasse Array2_Number

T_List2 Typdefinition für die gen. Klasse List2

T_List2_Node Typdefinition für die Klasse List2_Node

Anhang AKlassen der GGL

72

T_List2_Edge Typdefinition für die Klasse List2_Edge

T_List2_Number Typdefinition für die Klasse List2_Number

T_List2_Int Typdefinition für die Klasse List2_Int

T_List2_Double Typdefinition für die Klasse List2_Double

T_DoubleVector Typdefinition für die Klasse DoubleVector

T_DoubleMatrix Typdefinition für die Klasse DoubleMatrix

T_AbTree Typdefinition für die generische Klasse AbTree

T_Dictionary Typdefinition für die gen. Klasse Dictionary

T_BinHeap Typdefinition für die gen. Klasse BinHeap

T_FHeap Typdefinition für die gen. Klasse FHeap

T_PQueue Typdefinition für die gen. Klasse PQueue

T_SkipList Typdefinition für die gen. Klasse SkipList

T_SortSeq Typdefinition für die gen. Klasse SortSeq

T_Node Typdefinition für die Klasse Node

T_Edge Typdefinition für die Klasse Edge

T_Face Typdefinition für die Klasse Face

T_Graph Typdefinition für die gen. Klasse Graph

T_Graph_Float_Float Typdefinition für die Klasse Graph_Float_Float

T_NodeMap Typdefinition für die gen. Klasse NodeMap

T_NodeMap_Number Typdefinition für die gen. Klasse NodeMap_Number

T_EdgeMap Typdefinition für die gen. Klasse EdgeMap

T_EdgeMap_Number Typdefinition für die gen. Klasse EdgeMap_Number

T_FaceMap Typdefinition für die gen. Klasse FaceMap

T_FaceMap_Number Typdefinition für die gen. Klasse FaceMap_Number

T_NodeMap2 Typdefinition für die gen. Klasse NodeMap2

T_NodeMatrix Typdefinition für die gen. Klasse NodeMatrix

T_NodeMatrix_Number Typdefinition für die gen. Klasse NodeMatrix_Number

T_NodePartition Typdefinition für die Klasse NodePartition

T_NodeSet Typdefinition für die Klasse NodeSet

T_NodePq Typdefinition für die gen. Klasse NodePq

T_NodePq_Number Typdefinition für die gen. Klasse NodePq_Number

Anhang AKlassen der GGL

73

A.7 Package ggl.inst

Diese Package enthält ausschließlich statisch spezialisierte generische Klassen. In Anlehnung anC++ Templates werden sie oft auch abgekürzt Instantiierungen genannt (daher der Package-Name). Die Tabelle gibt für jede dieser Klassen die generische Basisklasse und die Elementklassenan. Die Basisklassen befinden sich in den Packages ggl.basic und ggl.graph. Zum Aufbaustatisch spezialisierter generischer Klassen und den damit verbundenen Ableitungsregeln sieheKapitel 3.4 „Generisches Programmieren mit Type“.

Array1_Int Array1 für Objekte der Klasse java.lang.Integer

Array2_Node Array2 für Objekte der Klasse Node

Array2_List2 Array2 für Objekte der Klasse ggl.basic.List2

Array2_List2Node Array2 für Objekte der Klasse List2_Node

List2_Str List2 für Objekte der Klasse java.lang.String

List2_Int List2 für Objekte der Klasse java.lang.Integer

List2_Double List2 für Objekte der Klasse java.lang.Double

List2_Node List2 für Objekte der Klasse Node

List2_Edge List2 für Objekte der Klasse Edge

List2_Face List2 für Objekte der Klasse Face

Dictionary_Str_Obj Dictionary mit Key: java.lang.String und Value: Object

Dictionary_Int_Int Dictionary mit java.lang.Integer für Key und Value

PQueue_Int_ObjPQueue mit Integer-Prioritäten und Informationseinträgender Klasse java.lang.Object

SortSeq_Int_Int SortSeq mit java.lang.Integer für Key- und Inf-Werte

Graph_Obj_Int Graph mit Object für Node- und Integer für Edge-Werte

Graph_Int_CharGraph mit java.lang.Integer für Node- undjava.lang.Character für Edge-Werte

Graph_Int_Int Graph mit java.lang.Integer für Node- und Edge-Werte

Graph_Int_Int_Int Graph mit Integer für Node-, Edge- und Face-Werte

Graph_Float_Float Graph mit java.lang.Float für Node- und Edge-Einträge

Graph_BigInt_DoubleGraph mit java.math.BigInteger für Node- undjava.lang.Double für Edge-Werte (Face-Werte der Klassejava.lang.Objekt = default)

Graph_Node_Int Graph mit Node für Node- und Integer für Edge-Werte

Graph_Node_Edge Graph mit Node für Node- und Edge für Edge-Werte

Anhang AKlassen der GGL

74

Graph_Node_List2Edge Graph mit Node für Node- und List2_Edge für Edge-Werte

Graph_Str_List2IntGraph mit String für Node- und List2_Integer für Edge-Werte

Graph_GFF_List2IntGraph mit Graph_Float_Float für Node- und List2_Int fürEdge-Werte

NodeMap_Bool NodeMap für Objekte der Klasse java.lang.Boolean

NodeMap_Int NodeMap für Objekte der Klasse java.lang.Integer

NodeMap_Double NodeMap für Objekte der Klasse java.lang.Double

NodeMap_BigInt NodeMap für Objekte der Klasse java.math.BigInteger

NodeMap_Node NodeMap für Objekte der Klasse Node

NodeMap_Edge NodeMap für Objekte der Klasse Edge

NodeMap_List2 NodeMap für Objekte der Klasse List2

NodeMap_List2Node NodeMap für Objekte der Klasse List2_Node

NodeMap_List2Edge NodeMap für Objekte der Klasse List2_Edge

EdgeMap_Bool EdgeMap für Objekte der Klasse java.lang.Boolean

EdgeMap_Char EdgeMap für Objekte der Klasse java.lang.Character

EdgeMap_Str EdgeMap für Objekte der Klasse java.lang.String

EdgeMap_Int EdgeMap für Objekte der Klasse java.lang.Integer

EdgeMap_Long EdgeMap für Objekte der Klasse java.lang.Long

EdgeMap_Float EdgeMap für Objekte der Klasse java.lang.Float

EdgeMap_Double EdgeMap für Objekte der Klasse java.lang.Double

EdgeMap_BigInt EdgeMap für Objekte der Klasse java.math.BigInteger

EdgeMap_Node EdgeMap für Objekte der Klasse Node

EdgeMap_Edge EdgeMap für Objekte der Klasse Edge

EdgeMap_List2 EdgeMap für Objekte der Klasse ggl.basic.List2

EdgeMap_List2Int EdgeMap für Objekte der Klasse List2_Int

EdgeMap_List2Double EdgeMap_List für Objekte der Klasse List2_Double

EdgeMap_List2Node EdgeMap für Objekte der Klasse List2_Double

FaceMap_Int FaceMap für Objekte der Klasse java.lang.Integer

NodeMap2_Bool NodeMap2 für Objekte der Klasse java.lang.Boolean

NodeMap2_Int NodeMap2 für Objekte der Klasse java.lang.Integer

NodeMatrix_Int NodeMatrix für Objekte der Klasse java.lang.Integer

Anhang AKlassen der GGL

75

NodeMatrix_Double NodeMatrix für Objekte der Klasse java.lang.Double

NodePq_Int NodePq mit Prioritäten der Klasse java.lang.Integer

NodePq_Double NodePq mit Prioritäten der Klasse java.lang.Double

A.8 Package ggl.test

Diese Package enthält Testprogramme für Performance- und Regressionstests. Die Angaben inKlammern benennen die Klassen, deren Routinen bei dem jeweiligen Test hauptsächlich geprüftwerden. Die Ausgabe des ersten Testszenarios des Regressionstest, ein Durchlauf derGglTestSuite , befindet sich im Ordner \\\\tttteeeesssstttt der GGL-Distribution zur Ansicht(GglRegTest01_stdout.txt). Aber natürlich gibt nur ein Blick in den Quellcode der einzelnenTestprogramme genauen Aufschluss darüber, was getestet wird. Ein paar zusätzliche Informationengibt es im Kapitel 3.8 „Das Testsystem“ und Benchmark-Ergebnisse werden im Kapitel 3.9"Performance" präsentiert.

GglRegTest Regressionstest der GGL

GglTestSuite Testsuite bestehend aus allen Einzeltests

BiconnectedTest Biconnected Components Test (graphAlg.BasicGraphAlg)

DijkstraTest Dijkstra Shortest Path Test (graphAlg.ShortestPath)

GenGridTest Grid Graph Generation Test (graph.GraphGen)

GitExampleTest Graph Iterator Example Test (graph.Graph)

GmlReadWriteTest GML Read Write Test (graph.Graph und graph.graphIO)

GraphReadWriteTest Graph Read Write Test in GGL-Format (graph.Graph)

JoinTest Graph Join Test (graph.Graph)

KuraTest Kuratowsky Test (graphAlg.PlaneGraphAlg)

MakeSimpleTest Make Simple Test (graph.Graph und graph.Misc)

MapIoTest Read Write Test bzgl. graph.NodeMap und graph.FaceMap

MapTest graph.NodeMap und graph.EdgeMap-Test

MatchTest Matching Test (graphAlg.MaxCardMatchBi)

MaxFlowTest Maximum Flow Test (ggl.graphAlg.MaxFlow)

MaxWeightMatchTest Max Weight Matching Test (graphAlg.maxWeightMatching)

McFlowTest Minimum Cost Flow Test (graphAlg.MinCostFlow)

MinSpanTest Minimum Spanning Tree Test (graphAlg.MinSpan)

Anhang AKlassen der GGL

76

OrthoTest Orthogonal Drawing Test (graphAlg.GraphDraw)

PlanarMapTest Planar Map Test (graph.Graph)

PlanarTest Planar Test (graphAlg.PlaneGraphAlg)

ShortTest1 Shortest Path Test 1 (graphAlg.ShortestPath)

ShortTest2 Shortest Path Test 2 (graphAlg.ShortestPath)

SLEmbedTest Straight Line Embedding Test (graphAlg.GraphDraw)

SpeedTest Spezieller Performance Test (graphAlg.*)

StrongCompTest Strongly Connected Components Test (BasicGraphAlg)

TestAll Großer Graphtest (graph.*, graphAlg.*)

TransClosureTest Transitive Closure Test (graphAlg.BasicGraphAlg)

Anhang BVerwendete Hilfsmittel

77

Anhang BVerwendete Hilfsmittel

Dieser Anhang ist eine Zusammenfassung der nicht trivialen verwendeten Hilfsmittel zurErstellung dieser Arbeit. Wichtig war vor allem ein leiser Rechner, viel RAM und viele großeFestplatten für die unzähligen Iterationen, die das Projekt durchlaufen hat, und natürlich für dieDatensicherung.

B.1 Verwendete Hardware (inkl. OS)

- Umax Pulsar mit G3/400-ProzessorkarteMac OS 8.6 (Hauptsystem)

- Apple Powerbook Titanium G4/667Mac OS X 10.2.4 (nur für Benchmarks und Kompatibilitätsprüfungen eingesetzt)

- Toshiba Notebook P4/500Windows 2000 (nur für Logiscope und Kompatibilitätsprüfungen eingesetzt)

B.2 Verwendete Programme

Textverarbeitung:

- Microsoft Word 2001

- BBEdit 6.0 (reiner, aber komplexer Texteditor)

- Adobe Acrobat 4.0 (für die Konvertierung der Dokumentation nach PDF)

- Macromedia Dreamweaver 4.01 (für die Nachkorrektur des HTML-Codes des API-Guides)

Entwicklungsumgebungen:

- Metrowerks CodeWarrior Pro Version 5.5 für C++ und Java

- Metrowerks CodeWarrior Pro Version 4 (nur eingesetzt, um den integrierten Java-Profiler fürLaufzeitoptimierungen zu nutzen; in CodeWarrior-Folgeversionen nicht mehr verfügbar)

- Mac OS Runtime vor Java 2.2.5 / JDK 1.1.8 (Java-Umgebung für Mac OS 8.6)

- Developer Tools für Mac OS X 10/2002, JDK 1.4.1 (Java-Umgebung für Mac OS X)

Anhang BVerwendete Hilfsmittel

78

Weitere Software-Werkzeuge:

- Telelogic Logiscope 5.0 RuleChecker(Tool zum Überprüfen von Coding Conventions)

- Telelogic Logiscope 5.0 TestChecker(Tool zum Überprüfen der Code Coverage von Testszenarien)

- Microsoft Internet Explorer 5.1 / Netscape 4.78 - 7(zum Überprüfen der API-Dokumentation)

- Macromedia Freehand 9.0 und Inspiration 6.0 (Inspiration Software) für das Erstellen von Grafiken

- KeyQuencer 2.5 der Firma BinarySoft, Quickeys der Firma CE Software undAppleScript der Firma Apple für das Automatisieren vieler Routineprozesse

B.3 Verwendeter Quellcode

- LEDA 3.8a C++ Quellcode (zum Portieren)

- JDK Quellcode (zur Inspektion der Klassenrelationen, des Kodierstils undImplementierungsdetails)

- JGL Quellcode (Java Generic Library von Objectspace / zur Inspektion des dort verwendetenKonzepts zur generischen Programmierung)

B.4 Hauptsächlich verwendete Literatur

LEDA:

- LEDA Manual 3.8a [LUM99]

- LEDA Guide 4.4 [LDG03]

Java:

- The Java Programming Language, Second Edition von Arnold/Gosling [AG99] (meine Bibel)

- JDK 1.1.8 API-Dokumentation von Sun [JDK98]

- The AmbySoft Inc. Coding Standards [Amb97]

- JGL API-Dokumentation von Objectspace, www.objectspace.com