Preview only show first 10 pages with watermark. For full document please download

Programmiermethoden - Hochschule Rheinmain

   EMBED


Share

Transcript

Programmiermethoden Sven Eric Panitz Hochschule RheinMain Version 451 Generiert von LectureNotes Teaching System 17. April 2016 Dieses Skript ist das kombinierte Skript zum Modul Programmiermethoden des Studiengangs WI und zu einem Teil des Moduls Programmiermethoden und Techniken im Studiengang AI. 2 Inhaltsverzeichnis 1 Iterierbare Objekte 1.1 Eine Schnittstelle zum Iterieren . . . . . . . . . . . . . 1.2 Eine generische Version der Iterationsschnittstelle . . . 1.3 Die Standard-Schnittstelle Iterator . . . . . . . . . . 1.4 Die Schnittstelle Iterable . . . . . . . . . . . . . . . . 1.4.1 Iterator als Innere Klasse . . . . . . . . . . . . 1.4.2 Verknüpfung zweier iterierbarer Objekte . . . . 1.5 Funktionen als Parameter . . . . . . . . . . . . . . . . 1.5.1 Schleifen als Methoden . . . . . . . . . . . . . . 1.5.2 Standard- (default) Methoden in Schnittstellen 1.5.3 Iterable als verzögerte Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 6 7 9 12 14 15 30 31 31 32 2 Hierarchische Strukturen 2.1 Einfach verkettete Listen . . . . . . . . . . 2.1.1 Formale Definition . . . . . . . . . 2.1.2 Implementierung . . . . . . . . . . 2.1.3 Mathematische Definition . . . . . 2.2 Bäume als verallgemeinerte Listen . . . . . 2.2.1 Formale Definition . . . . . . . . . 2.2.2 Implementierung einer Baumklasse 2.3 XML als serialisierte Baumstruktur . . . . 2.4 Das syntaktische XML-Format . . . . . . 2.4.1 Elementknoten . . . . . . . . . . . 2.4.2 Leere Elemente . . . . . . . . . . . 2.4.3 Gemischter Inhalt . . . . . . . . . 2.4.4 XML Dokumente sind Bäume . . . 2.4.5 Chracter Entities . . . . . . . . . . 2.4.6 CData Sections . . . . . . . . . . . 2.4.7 Kommentare . . . . . . . . . . . . 2.4.8 Processing Instructions . . . . . . . 2.4.9 Attribute . . . . . . . . . . . . . . 2.4.10 Zeichencodierungen . . . . . . . . . 2.4.11 DTD . . . . . . . . . . . . . . . . . 2.5 XML Verarbeitung . . . . . . . . . . . . . 2.5.1 Das DOM Api . . . . . . . . . . . 2.5.2 XPath . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 41 41 43 44 53 54 54 67 69 69 70 70 71 72 73 73 74 74 75 76 78 78 81 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Inhaltsverzeichnis 2.5.3 2.5.4 2.5.5 XSLT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Das SAX-API . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Das StAx-API . . . . . . . . . . . . . . . . . . . . . . . . . 105 3 Eine graphische Komponente für Baumstrukturen 4 107 Kapitel 1 Iterierbare Objekte Eine häufig zu lösende Aufgabe in der Programmierung ist es, durch bestimmte Elemente zu iterieren. Für die Iteration stehen die Schleifenkonstrukte zur Verfügung. Eine Iteration (lateinisch iteratio, die Wiederholung), soll nacheinander einen bestimmten Code-Block für verschiedene Elemente durchführen. In klassischer Weise wird zum Iterieren eine Indexvariable in einer Schleife durchgezählt. Mit dem Index wird dann in einem Schleifenrumpf jeweils etwas gerechnet. Eine klassische Schleife zum Durchlaufen der geraden Zahlen kleiner 10: 1 2 3 4 5 6 7 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c c l a s s I t e r a t i o n 1 { p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( i n t i = 2 ; i <10; i = i +2){ System . out . p r i n t l n ( i ) ; } } } Listing 1.1: Iteration1.java Das obige Beispiel ist in keiner Weise ein objektorientiertes Beispiel. Es ist eine ganz einfache Schleife, wie aus der imperativen Programmierung gewohnt. In einer objektorientierten Umsetzung wird stets versucht, ein Objekt zu definieren, das seine Funktionalität kapselt. In diesem Fall ein Objekt, das weiß, wie zu iterieren ist. Hierzu kann man versuchen alle im Kopf der for-Schleife gebündelten Informationen in einem Objekt zu kapseln. In der for-Schleife gibt es zunächst eine Zählvariable i. Dann einen Test, ob die Schleife noch weiter durchlaufen werden soll, und eine Anweisung, wie sich die Schleifenvariable verändert, wenn es zum nächsten Durchlauf geht. Packt man diese drei Informationen in ein Objekt, so erhalten wir die folgende Klasse: 1 2 3 4 5 package name . p a n i t z . pmt . i t e r a t i o n ; public class GeradeZahlenKleiner10Iterierer { i n t i ; // d i e S c h l e i f e n v a r i a b l e GeradeZahlenKleiner10Iterierer ( int i ){ t h i s . i = i ; // I n i t i a l i s i e r u n g d e r S c h l e i f e n v a r i a b l e 5 Kapitel 1 Iterierbare Objekte } boolean s c h l e i f e n T e s t ( ) { r e t u r n i < 1 0 ; // Test u e b e r d i e S c h l e i f e n v a r i a b l e } void s c h l e i f e W e i t e r s c h a l t e n ( ) { i = i + 2 ; // f u e r n a e c h s t e n S c h l e i f e n d u r c h l a u f } 6 7 8 9 10 11 12 Listing 1.2: GeradeZahlenKleiner10Iterierer.java Im Schleifenrumpf wird der aktuelle Wert der Iteration benutzt. In der obigen forSchleife ist das die Zahl i. Auch hierfür können wir noch eine Methode vorsehen. int schleifenWert () { return i ; } 13 14 15 Listing 1.3: GeradeZahlenKleiner10Iterierer.java Mit einem Objekt dieser Klasse können wir jetzt eine Schleife durchlaufen, ebenso wie in der ersten Schleife oben: p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { for ( GeradeZahlenKleiner10Iterierer i t = new G e r a d e Z a h l e n K l e i n e r 1 0 I t e r i e r e r ( 0 ) ; it . schleifenTest () ; it . schleifeWeiterschalten () ){ System . out . p r i n t l n ( i t . s c h l e i f e n W e r t ( ) ) ; } } 16 17 18 19 20 21 22 23 24 } Listing 1.4: GeradeZahlenKleiner10Iterierer.java 1.1 Eine Schnittstelle zum Iterieren Noch ist überhaupt kein Vorteil darin erkennbar, dass wir die Komponenten, die zur Programmierung einer Schleife benötigt werden (Initialisierung des Iterierungsobjektes, Test, Weiterschaltung, Abfragen des Schleifenelementes) in einem Objekt gekapselt haben. Der Vorteil der Objektorientierung liegt in zusätzlichen Abstraktionen. Eine der stärksten Abstraktionen in Java sind Schnittstellen. Naheliegend ist es, die Methoden der obigen Klasse in einer Schnittstelle vorzugeben: 1 2 3 4 package name . p a n i t z . pmt . i t e r a t i o n ; public interface I n t I t e r i e r e r { boolean s c h l e i f e n T e s t ( ) ; void s c h l e i f e W e i t e r s c h a l t e n ( ) ; 6 1.2 Eine generische Version der Iterationsschnittstelle Integer schleifenWert () ; 5 6 } Listing 1.5: IntIterierer.java Die Klasse oben lässt sich nun als Implementierung dieser Schnittstelle schreiben. Um die Klasse dabei noch ein wenig allgemeiner zu schreiben, übergeben wir jetzt auch die obere Schranke der Iteration im Konstruktor. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c c l a s s G e r a d e Z a h l e n I t e r i e r e r implements I n t I t e r i e r e r { i n t from ; i n t to ; G e r a d e Z a h l e n I t e r i e r e r ( i n t from , i n t t o ) { t h i s . from = from ; t h i s . to = to ; } public boolean s c h l e i f e n T e s t ( ) { r e t u r n from < t o ; } public void s c h l e i f e W e i t e r s c h a l t e n ( ) { from = from + 2 ; } public Integer schleifenWert () { r e t u r n from ; } Listing 1.6: GeradeZahlenIterierer.java Eine Schleife für dieses Klasse sieht jetzt wie folgt aus. Man erkennt, dass die Schleife nur noch für die Initialisierung des Objektes der Iteration anwendungsspezifischen Code hat. Ansonsten liegt die Anwendungslogik komplett im Iterations-Objekt. p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( I n t I t e r i e r e r i t = new G e r a d e Z a h l e n I t e r i e r e r ( 0 , 1 0 ) ; it . schleifenTest () ; it . schleifeWeiterschalten () ){ System . out . p r i n t l n ( i t . s c h l e i f e n W e r t ( ) ) ; } } 18 19 20 21 22 23 24 25 } Listing 1.7: GeradeZahlenIterierer.java 1.2 Eine generische Version der Iterationsschnittstelle Eine weitere Abstraktion, die von heutigen Programmiersprachen angeboten wird, liegt in der Möglichkeit, Typen variabel zu halten über generische Klassen und 7 Kapitel 1 Iterierbare Objekte Schnittstellen. Statt eine Iterations-Schnittstelle für ganze Zahlen zu definieren, lässt sich allgemein eine solche für beliebig aber feste Typen definieren: 1 2 3 4 5 6 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c i n t e r f a c e G e n e r i s c h e r I t e r i e r e r { boolean s c h l e i f e n T e s t ( ) ; void s c h l e i f e W e i t e r s c h a l t e n ( ) ; A schleifenWert () ; } Listing 1.8: GenerischerIterierer.java Beim Implementieren dieser generischen Schnittstelle gilt es jetzt zu entscheiden, welcher konkrete Typ für die Typvariable eingesetzt werden soll, also was für einen Typ die Elemente haben, über die iteriert werden soll. In unserem Beispielfall sind dieses ganze Zahlen. Da für Typvariablen in generischen Typen keine primitiven Typen eingesetzt werden dürfen, benutzen wir hier die Klasse Integer und vertrauen an mehreren Stellen darauf, dass intern Daten vom Typ int und Integer gegenseitig konvertiert werden. Dieses wird als automatisches Boxing/Unboxing bezeichnet. Die neue Implementierung der Iterationsklasse sieht entsprechend wie folgt aus: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package name . p a n i t z . pmt . i t e r a t i o n ; public class GeradeZahlenIterierer2 implements G e n e r i s c h e r I t e r i e r e r { i n t from ; i n t to ; G e r a d e Z a h l e n I t e r i e r e r 2 ( i n t from , i n t t o ) { t h i s . from = from ; t h i s . to = to ; } public boolean s c h l e i f e n T e s t () { r e t u r n from < t o ; } public void s c h l e i f e W e i t e r s c h a l t e n ( ) { from = from + 2 ; } public Integer schleifenWert () { r e t u r n from ; } Listing 1.9: GeradeZahlenIterierer2.java An der Benutzung ändert sich in diesem Fall wenig, gegenüber der nicht generischen Version. p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( G e n e r i s c h e r I t e r i e r e r i t = new G e r a d e Z a h l e n I t e r i e r e r 2 ( 0 , 1 0 ) 19 20 21 8 1.3 Die Standard-Schnittstelle Iterator ; it . schleifenTest () ; it . schleifeWeiterschalten () ){ int i = i t . schleifenWert () ; System . out . p r i n t l n ( i ) ; 22 23 24 25 } 26 } 27 28 } Listing 1.10: GeradeZahlenIterierer2.java Gewonnen haben wir nun die Möglichkeit, auf gleiche Weise Iteratorobjekte zu implementieren, deren Elemente andere Typen haben. 1.3 Die Standard-Schnittstelle Iterator Die im vorangegangenen Abschnitt entwickelte Schnittstelle GenerischerIterierer hat ein Pendant in Javas Standard-API. Im Paket java.util findet sich dort ebenfalls eine generische Schnittstelle, die beschreibt, dass ein Objekt zum Iterieren benutzt werden kann, die Schnittstelle Iterator. Diese ist allerdings ein wenig anders aufgebaut, als unsere Schnittstelle. Sie definiert drei Methoden: • boolean hasNext(): diese Methode entspricht ziemlich genau unserer Methode schleifenTest. Sie ergibt true, wenn es noch weitere Elemente zum Iterieren gibt. • A next(): diese Methode vereint die zwei Methoden schleifenWert() und schleifeWeiterschalten unserer Schnittstelle. Es wird dabei das aktuelle Element zurück gegeben und gleichzeitig intern der Schritt weiter zum nächsten Element vorgenommen. Dieser Schritt kann nicht mehr rückgängig gemacht werden. • void remove(): diese Methode ist in der Schnittstelle als optional bezeichnet. Mit ihr soll es möglich sein, das aktuelle Element aus dem Objekt, über das iteriert wird, zu löschen. Handelt es sich bei dem Iterator um einen Iterator, der über die Elemente einer Liste iteriert, soll also das entsprechende Element aus der Liste gelöscht werden. In unserem Fall der Iteration über einen Bereich der ganzen Zahlen ergibt diese Operation keinen Sinn. Genau aus diesem Grund wurde sie in der Dokumentation als optional markiert. Um unsere Überlegungen zu einer Iterationsschnittstelle mit der Standardschnittstelle Iterator zu verbinden, bietet sich an, eine abstrakte Klasse zu definieren, die unsere drei Methoden als abstrakte, noch zu implementierende Methoden vorgibt, die Methoden der Standardschnittstelle mit Hilfe der abstrakten Methoden aber bereits implementiert. Wir definieren zunächst die drei bereits bekannten abstrakten Methoden: 9 Kapitel 1 Iterierbare Objekte 1 2 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c a b s t r a c t c l a s s A b s t r a c t I t e r a t o r implements j a v a . u t i l . I t e r a t o r { 3 public abstract boolean s c h l e i f e n T e s t ( ) ; public abstract void s c h l e i f e W e i t e r s c h a l t e n ( ) ; public abstract A schleifenWert () ; 4 5 6 Listing 1.11: AbstractIterator.java Nun können wir mit diesen die Methoden der Standardschnittstelle Iterator umsetzen. Beginnen wir mit der Methode next(): p u b l i c A next ( ) { A result = schleifenWert () ; schleifeWeiterschalten () ; return r e s u l t ; } 7 8 9 10 11 Listing 1.12: AbstractIterator.java Man sieht genau die Bedeutung der Methode next() der aktuelle Schleifenwert wird zurück gegeben. zusätzlich wird der Iterator weiter geschaltet, so dass ein nächster Aufruf das darauffolgende Iterationselement zurück gibt. Die Methode schleifenTest() entspricht exakt der Methode hasNext() der Standardschnittstelle. Deshalb können wir in der Implementierung unsere Methode direkt aufrufen. p u b l i c b o o l e a n hasNext ( ) { return schleifenTest () ; } 12 13 14 Listing 1.13: AbstractIterator.java Schließlich sind wir noch gezwungen die Methode remove() zu implementieren. Da sie optional ist und wir sie in unseren Beispielen nicht sinnvoll umsetzen können, soll nur eine Laufzeitausnahme geworfen werden. p u b l i c v o i d remove ( ) { throw new Unsu pp ortedOperati onExc eptio n ( ) ; } 15 16 17 18 } Listing 1.14: AbstractIterator.java Um wieder an unseren konkreten Iterator über einen bestimmten Zahlenbereich zu kommen, können wir jetzt eine konkrete Unterklasse dieser abstrakten Klasse 10 1.3 Die Standard-Schnittstelle Iterator schreiben. Diese implementiert dann automatisch über die geerbten Methoden auch die Standardschnittstelle Iterator. Um noch ein wenig flexibler zu werden, sei noch ein drittes Feld der Klasse zugefügt, dass die Schrittweite beim Weiterschalten des Iterators speichert. 1 2 3 4 5 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c c l a s s I n t e g e r R a n g e I t e r a t o r e x t e n d s A b s t r a c t I t e r a t o r { i n t from ; i n t to ; int step ; 6 I n t e g e r R a n g e I t e r a t o r ( i n t from , i n t to , i n t s t e p ) { t h i s . from = from ; t h i s . to = to ; this . step = step ; } public boolean s c h l e i f e n T e s t ( ) { r e t u r n from <= t o ; } public void s c h l e i f e W e i t e r s c h a l t e n ( ) { from = from + s t e p ; } public Integer schleifenWert () { r e t u r n from ; } 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Listing 1.15: IntegerRangeIterator.java Damit können wir jetzt in der standardmäßig in Java empfohlenen Art und Weise mit dem Iterationsobjekt über die Elemente mit einer for-Schleife iterieren. Der Iterator wird initialisiert und seine Methode hasNext() zum Schleifentest benutzt. Das Weiterschalten wird nicht im Kopf der for-Schleife vorgenommen, sondern als erster Befehl im Rumpf, indem dort die Methode next() als erste Anweisung aufgerufen wird. p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( j a v a . u t i l . I t e r a t o r i t = new I n t e g e r R a n g e I t e r a t o r (0 ,10 ,2) ; i t . hasNext ( ) ; ) { i n t i = i t . next ( ) ; System . out . p r i n t l n ( i ) ; } } 21 22 23 24 25 26 27 28 } Listing 1.16: IntegerRangeIterator.java 11 Kapitel 1 Iterierbare Objekte 1.4 Die Schnittstelle Iterable Bisher haben wir definiert, was unter Iterator-Objekten zu verstehen ist. In Javas Standard-API gibt es eine weitere Schnittstelle, die sich mit dem Konzept der Iteration beschäftigt. Es handelt sich dabei um die Schnittstelle Iterable. Diese liegt nicht im Paket java.util sondern im Standardpaket java.lang. Sie muss also nicht explizit importiert werden, wenn man sie benutzen will. Auch die Schnittstelle Iterable ist generisch. Sie soll ausdrücken, dass ein Objekt iterierbar ist, in dem Sinne, dass es ein Iterator-Objekt gibt, mit dessen Hilfe über die Elemente des Objekts iteriert werden kann. Deshalb kommt die Schnittstelle Iterable zunächst (im nächsten Abschnitt werden wir sehen, das dem bald nicht mehr so ist) mit einer einzigen Methode aus. Die Methode heißt iterator() und ist dazu gedacht, das Iteratorobjekt für die Klasse zu erfragen. Typische Beispiele sind hierbei natürlich die klassischen Sammlungsklassen, wie Listen und Mengen. Diese implementieren alle die Schnittstelle Iterable. Bleiben wir zunächst bei unserem durchgängigen Beispiel. Statt jetzt direkt die Iteratorklasse für einen Zahlenbereich zu definieren, können wir zunächst eine Klasse definieren, die nur einen Zahlenbereich beschreibt. Wir lassen sie aber die Schnittstelle Iterable implementieren. Erst der Aufruf der Methode iterator() erzeugt dann ein entsprechendes Iterator-Objekt. 1 2 3 4 5 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c c l a s s I n t e g e r R a n g e implements I t e r a b l e { i n t from ; i n t to ; int step ; 6 p u b l i c I n t e g e r R a n g e ( i n t from , i n t to , i n t s t e p ) { t h i s . from = from ; t h i s . to = to ; this . step = step ; } 7 8 9 10 11 Listing 1.17: IntegerRange.java Um diese Klasse noch etwas flexibler benutzen zu können, sehen wir zwei weitere Konstruktoren vor. Diese setzen die Schrittweite und die obere Schranke auf Standardwerte: p u b l i c I n t e g e r R a n g e ( i n t from , i n t t o ) { t h i s ( from , to , 1 ) ; } p u b l i c I n t e g e r R a n g e ( i n t from ) { t h i s ( from , I n t e g e r .MAX_VALUE, 1 ) ; } 12 13 14 15 16 17 12 1.4 Die Schnittstelle Iterable Listing 1.18: IntegerRange.java Die Klasse zum Iterieren über einen Zahlenbereich haben wir bereits entwickelt. Diese kann nun für die Methode iterator() benutzt werden. p u b l i c j a v a . u t i l . I t e r a t o r i t e r a t o r ( ) { r e t u r n new I n t e g e r R a n g e I t e r a t o r ( from , to , s t e p ) ; } 18 19 20 Listing 1.19: IntegerRange.java Java hat eine syntaktische Besonderheit für Objekte, die die Schnittstelle Iterable implementieren, eingeführt. Eine besondere Art der Schleife, die als for-each Schleife bezeichnet wird. Diese hat im Schleifenrumpf zwei Komponenten, die durch einen Doppelpunkt getrennt werden. Nach dem Doppelpunkt steht das iterierbare Objekt, vor dem Doppelpunkt wird die Variable definiert, an der in jedem Schleifendurchlauf das aktuelle Element gebunden ist. Für unser Beispiel erhalten wir dann die folgende kompakte Schleife: p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { I n t e g e r R a n g e i s = new I n t e g e r R a n g e ( 0 , 1 0 , 2 ) ; for ( int i : is ){ System . out . p r i n t l n ( i ) ; } 21 22 23 24 25 Listing 1.20: IntegerRange.java Gelesen wird dieses Konstrukt als: Für alle Zahlen i aus dem iterierbaren Objekt is führe den Schleifenrumpf aus. Im Vergleich hierzu, sei hier noch einmal die entsprechende Schleife ohne Verwendung der for-each Schleife geschrieben. In diesem Fall wird explizit nach dem Iterator-Objekt gefragt. f o r ( j a v a . u t i l . I t e r a t o r i t = i s . i t e r a t o r ( ) ; i t . hasNext ( ) ; ) { i n t i = i t . next ( ) ; System . out . p r i n t l n ( i ) ; } 26 27 28 29 } 30 31 } Listing 1.21: IntegerRange.java 13 Kapitel 1 Iterierbare Objekte 1.4.1 Iterator als Innere Klasse Da in der Regel die Iteratorklasse fest an der Klasse gebunden ist, über deren Elemente iteriert werden soll, bietet sich an, die Iteratorklasse zu verstecken. In Java kann hierfür eine innere Klasse benutzt werden. Mit einem Sichtbarkeitsattribut private ist dann die Iteratorklasse komplett versteckt: Wir sehen also zunächst die Klasse mit den notwendigen Feldern für die beschreibung des Iterationsbereichs vor: 32 33 package name . p a n i t z . pmt . u t i l ; import j a v a . u t i l . I t e r a t o r ; 34 35 36 37 38 p u b l i c c l a s s I n t e g e r R a n g e implements I t e r a b l e { i n t from ; i n t to ; int step ; 39 p u b l i c I n t e g e r R a n g e ( i n t from , i n t to , i n t s t e p ) { t h i s . from = from ; t h i s . to = to ; this . step = step ; } 40 41 42 43 44 45 p u b l i c I n t e g e r R a n g e ( i n t from , i n t t o ) { t h i s ( from , to , 1 ) ; } p u b l i c I n t e g e r R a n g e ( i n t from ) { t h i s ( from , I n t e g e r .MAX_VALUE, 1 ) ; } 46 47 48 49 50 51 Listing 1.22: IntegerRange.java Für diese Klasse kann ein Iterator-Objekt erzeugt werden: p u b l i c I t e r a t o r i t e r a t o r ( ) { r e t u r n new I n t e g e r R a n g e I t e r a t o r ( from , to , s t e p ) ; } 52 53 54 Listing 1.23: IntegerRange.java Das Iterator-Objekt ist dabei von einer inneren Klasse, die beschreibt, wie durch die Elemente iteriert wird: p r i v a t e s t a t i c c l a s s I n t e g e r R a n g e I t e r a t o r implements I t e r a t o r < I n t e g e r >{ i n t from ; i n t to ; int step ; 55 56 57 58 59 14 1.4 Die Schnittstelle Iterable I n t e g e r R a n g e I t e r a t o r ( i n t from , i n t to , i n t s t e p ) { t h i s . from = from ; t h i s . to = to ; this . step = step ; } p u b l i c b o o l e a n hasNext ( ) { r e t u r n from < t o ; } p u b l i c I n t e g e r next ( ) { i n t r e s u l t = from ; from = from + s t e p ; return r e s u l t ; } p u b l i c v o i d remove ( ) {} 60 61 62 63 64 65 66 67 68 69 70 71 72 73 } 74 75 } Listing 1.24: IntegerRange.java 1.4.2 Verknüpfung zweier iterierbarer Objekte In diesem Abschnitt werden wir aus zwei iterierbaren Objekten eines machen. Das neue Objekte soll erst durch die Elemente des einen iterierebaren Objekts und anschließend durch die Elemente des zweiten iterieren. Es ist also ein Aneinanderhängen der beiden Iterables. Fast wie bei Listen, die aneinander gehängt werden. Hierzu sei die Klasse Append entwickelt. Objekte dieser Klasse sollen aus zwei iterierbaren Objekten ein neues erzeugen. Somit ist sie als generische Klasse, die Iterable implementiert entworfen: 1 2 package name . p a n i t z . pmt . u t i l ; import j a v a . u t i l . I t e r a t o r ; 3 4 p u b l i c c l a s s Append implements I t e r a b l e { Listing 1.25: Append.java Die Klasse Append soll zwei iterierbare Objekte zu einem neuen verbinden. Hierzu braucht sie Felder, um die beiden zu verkknüpfenden Objekte zu speichern: 5 6 I t e r a b l e xs ; I t e r a b l e ys ; Listing 1.26: Append.java In gewohnter Weise ist ein Konstruktor vorzusehen, der diese Felder initialisiert. 15 Kapitel 1 Iterierbare Objekte p u b l i c Append ( I t e r a b l e xs , I t e r a b l e ys ) { t h i s . xs = xs ; t h i s . ys = ys ; } 7 8 9 10 Listing 1.27: Append.java Es muss schließlich eine Methode geben, die den neuen Iterator zurück gibt, der durch beide Objekte iteriert. @Override p u b l i c I t e r a t o r i t e r a t o r ( ) { r e t u r n new MyIt ( ) ; } 11 12 13 14 Listing 1.28: Append.java Die hier instantiierte Klasse MyIt wird als innere Klasse der Klasse Append realisiert: p u b l i c c l a s s MyIt implements I t e r a t o r { 15 Listing 1.29: Append.java Der neue Iterator muss die beiden ursprünglichen Iteratoren kennen. Daher werden zwei Felder für diese vorgesehen. Initialisiert werden die Felder mit den Iteratoren, die die beiden zu verknüpfenden iterierbaren Objekte der äußeren Klasse Append zurück geben: I t e r a t o r x s I t = xs . i t e r a t o r ( ) ; I t e r a t o r y s I t = ys . i t e r a t o r ( ) ; 16 17 Listing 1.30: Append.java Es bleiben die Methoden der Schnittstelle Iterator, die implementiert werden müssen. Es stellt sich die Methode hasNext() als besonders einfach heraus. Der neue Iterator hat noch weitere Elemente, wenn einer der beiden ursprünglichen Iteratoren noch ein nächstes Element hat. @Override p u b l i c b o o l e a n hasNext ( ) { r e t u r n x s I t . hasNext ( ) | | y s I t . hasNext ( ) ; } 18 19 20 21 Listing 1.31: Append.java Wo holt der neue Iterator sein nächstes Element her? Wenn es noch weitere Elemente im ersten Iterator gibt, dann aus diesem, ansonsten aus dem zweiten Iterator. 16 1.4 Die Schnittstelle Iterable @Override p u b l i c E next ( ) { i f ( x s I t . hasNext ( ) ) r e t u r n x s I t . next ( ) ; r e t u r n y s I t . next ( ) ; } 22 23 24 25 26 Listing 1.32: Append.java Auf einer Implementierung der Methode remove() verzichten wir wieder. @Override p u b l i c v o i d remove ( ) {} 27 } 28 29 } Listing 1.33: Append.java In einer kleinen Testklasse soll die Klasse Append an zwei Beispielen ausprobiert werden. 1 package name . p a n i t z . pmt . u t i l ; Listing 1.34: AppendTest.java Einer der beiden Beispielaufrufe soll mit Standardlisten gemacht werden, so dass diese importiert werden. 2 3 4 5 6 import import import import import java . u t i l . ArrayList ; java . u t i l . I t e r a t o r ; java . u t i l . LinkedList ; java . u t i l . List ; name . p a n i t z . pmt . i t e r a t i o n . I n t e g e r R a n g e ; Listing 1.35: AppendTest.java Im ersten Beispielaufruf sollen zwei iterierte Zahlenbereiche nacheinander iteriert werden: 7 8 9 10 11 p u b l i c c l a s s AppendTest { p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( i n t i : new Append<>(new I n t e g e r R a n g e ( 1 , 1 5 ) , new I n t e g e r R a n g e (47 ,52) ) ){ System . out . p r i n t l n ( i ) ; } Listing 1.36: AppendTest.java Im zweiten Beispielaufruf werden als iterierbare Objekte zunächst Listen erzeugt: 17 Kapitel 1 Iterierbare Objekte L i s t xs = new L i n k e d L i s t <>() ; xs . add ( ” Freunde ” ) ; xs . add ( ” Roemer ” ) ; xs . add ( ” L a n d s l e u t e ” ) ; 12 13 14 15 16 L i s t ys = new A r r a y L i s t <>() ; ys . add ( ” l e i h t ” ) ; ys . add ( ” mir ” ) ; ys . add ( ” Euer ” ) ; ys . add ( ”Ohr” ) ; 17 18 19 20 21 Listing 1.37: AppendTest.java Diese beiden Listen lassen sich nun auch zu einem neuen iterierbaren Objekt mit Append verbinden: f o r ( S t r i n g s : new Append<>(xs , ys ) ) { System . out . p r i n t l n ( s . toUpperCase ( ) ) ; } 22 23 24 } 25 26 } Listing 1.38: AppendTest.java Aufgabe 1 In dieser Aufgabe sollen Sie Klassen schreiben, die die Schnittstelle Iterable implementieren und es ermöglichen über unterschiedliche Daten zu iterieren. Der Iterator soll dabei jeweils als private innere Klasse realisiert werden. a) Schreiben Sie noch einmal, wie bereits in diesem Kapitel vorgemacht, eine Iterable-Klasse, die es es erlaubt über einen Zahlenbereich zu iterieren. Nennen Sie die Klasse IntRange und sehen Sie viele verschiedene Konstruktoren vor: • IntRange(): es wird endlos über alle int Werte beginnend bei 0 iteriert. • IntRange(int from): es wird endlos über alle int Werte beginnend bei from iteriert. • IntRange(int from, int to): es wird beginnend bei from bis einschließlich to iteriert. • IntRange(int from, int to, int step): es wird beginnend bei from bis einschließlich to in step Schritten iteriert. Lösung 1 2 18 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; 1.4 Die Schnittstelle Iterable 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 p u b l i c c l a s s IntRange implements I t e r a b l e { i n t from ; i n t to ; int step ; boolean i n f i n i t e ; p u b l i c IntRange ( i n t from , i n t to , int step ){ t h i s . from = from ; t h i s . to = to ; this . step = step ; infinite = false ; } p u b l i c IntRange ( i n t from , i n t to ) { t h i s ( from , to , 1 ) ; } p u b l i c IntRange ( i n t from ) { t h i s ( from , 0 ) ; i n f i n i t e = true ; } p u b l i c IntRange ( ) { this (1) ; } @Override p u b l i c I t e r a t o r i t e r a t o r ( ) { r e t u r n new I t e r ( from ) ; } p r i v a t e c l a s s I t e r implements I t e r a t o r { i n t from ; I t e r ( i n t from ) { t h i s . from = from ; } @Override p u b l i c b o o l e a n hasNext ( ) { r e t u r n i n f i n i t e | | from<= t o ; } @Override p u b l i c I n t e g e r next ( ) { i n t r e s u l t = from ; from = from+s t e p ; return r e s u l t ; } } } Listing 1.39: IntRange.java b) Schreiben Sie eine Klasse Fib, die Iterable so implementiert, dass beim Aufruf der Methode next nacheinander die Fibonaccizahlen zurück gegeben werden. Lösung Da die Fibonaccizahlen so schnell wachsen, dass sie nach wenigen Zahlen den Bereich des Datentyps Integer sprengen, hier abweichend von der Aufgabenstellung eine Lösung, die die Klasse BigInteger benutzt und somit beliebig große Zahlen berechnen kann. 1 package name . p a n i t z . u t i l ; 19 Kapitel 1 Iterierbare Objekte 2 3 4 5 6 7 8 9 10 import j a v a . u t i l . I t e r a t o r ; import j a v a . math . B i g I n t e g e r ; p u b l i c c l a s s Fib implements I t e r a b l e { @Override p u b l i c I t e r a t o r i t e r a t o r ( ) { r e t u r n new I t e r ( ) ; } p r i v a t e c l a s s I t e r implements I t e r a t o r { B i g I n t e g e r n2 = B i g I n t e g e r . v a l u e O f ( 0 ) ; B i g I n t e g e r n1 = B i g I n t e g e r . v a l u e O f ( 1 ) ; 11 @Override p u b l i c b o o l e a n hasNext ( ) { return true ; } @Override p u b l i c B i g I n t e g e r next ( ) { B i g I n t e g e r tmp = n2 . add ( n1 ) ; n2 = n1 ; n1 = tmp ; r e t u r n n2 ; } 12 13 14 15 16 17 18 19 20 } p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { new Fib ( ) . f o r E a c h ( x−> System . out . p r i n t l n ( x ) ) ; } 21 22 23 24 25 } Listing 1.40: Fib.java c) Schreiben Sie eine Klasse IterableString. Sie soll im Konstruktor ein String-Objekt erhalten. Die Klasse soll Iterable so implementieren, dass über die einzelnen Zeichen des Strings iteriert wird. Lösung Die iterierbare Klasse bekommt einen String im Konstruktor übergeben. Als Iterator wird ein Objekt einer inneren Iteratorklasse erzeugt: 1 2 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; 3 4 5 public class I t e r a b l e S t r i n g implements I t e r a b l e { f i n a l private String str ; 6 7 8 9 public IterableString ( String str ){ this . str = str ; } 10 11 12 13 p u b l i c I t e r a t o r i t e r a t o r ( ) { r e t u r n new I t e r ( ) ; } Listing 1.41: IterableString.java 20 1.4 Die Schnittstelle Iterable Nun wird die innere Iteratorklasse definiert. Diese hat ein int Feld, in dem der Iterator wich merkt, wie oft schon das nächste Element erfragt wurde. p r i v a t e c l a s s I t e r implements I t e r a t o r { private int i = 0; 14 15 Listing 1.42: IterableString.java Wenn bereits alle Zeichen des Strings mit next() erfragt wurden, dann gibt es kein nächstes Element. p u b l i c b o o l e a n hasNext ( ) { return i < str . length () ; } 16 17 18 Listing 1.43: IterableString.java Um das nächste Element zurück zu geben, wird das i-te Element des Strings erfragt. Anschließend wird i um 1 erhöht. p u b l i c C h a r a c t e r next ( ) { r e t u r n s t r . charAt ( i ++) ; } 19 20 21 } 22 23 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { f o r ( c h a r c : new I t e r a b l e S t r i n g ( ” H e l l o world ! ” ) ) { System . out . p r i n t l n ( c ) ; } } 24 25 26 27 28 29 } Listing 1.44: IterableString.java d) Schreiben Sie eine Klasse Lines. Sie soll Iterable so implementieren, dass nacheinander die Zeilen eines im Konstruktor übergebenen Strings bei der Iteration durchlaufen werden. Lösung 1 2 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; 3 4 5 6 7 8 p u b l i c c l a s s L i n e s implements I t e r a b l e { s t a t i c S t r i n g NEW_LINE = System . g e t P r o p e r t y ( ” l i n e . s e p a r a t o r ”) ; String [ ] l i n e s ; public Lines ( String s t r ) { l i n e s = s t r . s p l i t (NEW_LINE) ; 21 Kapitel 1 Iterierbare Objekte } 9 10 @Override p u b l i c I t e r a t o r i t e r a t o r ( ) { r e t u r n new MyIt ( ) ; } 11 12 13 14 p u b l i c c l a s s MyIt implements I t e r a t o r { int i = 0; @Override p u b l i c b o o l e a n hasNext ( ) { r e t u r n i so implementieren, dass über die einzelnen Zeichen, die ein im Konstruktor übergebenes Reader-Objekt liefert, iteriert wird. Bei der Lösung dieser Aufgabe ergibt sich ein Problem. Ein Reader kann nur einmal zum Iterieren benutzt werden. Würde man erlauben, das zwei Iteratoren die Methode desselben Readers benutzen, um das nächste Element zu holen, dann würden beide Iteratoren implizit ein Zeichen weiter geschaltet werden. Daher werden wir verbieten, dass zweimal die Methode iterator() aufgerufen wird. Beim zweiten Mal wird dann eine Ausnahme geworfen. Lösung Bei der Lösung dieser Aufgabe ergibt sich ein Problem. Ein Reader kann nur einmal zum Iterieren benutzt werden. Würde man erlauben, das zwei Iteratoren die Methode desselben Readers benutzen, 22 1.4 Die Schnittstelle Iterable um das nächste Element zu holen, dann würden beide Iteratoren implizit ein Zeichen weiter geschaltet werden. Daher werden wir verbieten, dass zweimal die Methode iterator() aufgerufen wird. Beim zweiten Mal wird dann eine Ausnahme geworfen. 1 2 3 4 5 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; import j a v a . i o . Reader ; import j a v a . i o . IOException ; import j a v a . i o . F i l e R e a d e r ; 6 7 8 9 p u b l i c c l a s s R e a d e r I t e r a b l e implements I t e r a b l e { f i n a l p r i v a t e Reader i n ; boolean iteratorGiven = f a l s e ; 10 11 12 13 p u b l i c R e a d e r I t e r a b l e ( Reader i n ) { this . in = in ; } 14 15 16 17 18 19 20 21 p u b l i c I t e r a t o r i t e r a t o r ( ) { i f ( iteratorGiven ) throw new RuntimeException ( ” R e a d e r I t e r a b l e can o n l y be i t e r a t e d once ” ) ; iteratorGiven = true ; r e t u r n new I t e r ( ) ; } 22 23 24 25 26 27 28 29 30 31 32 33 34 p r i v a t e c l a s s I t e r implements I t e r a t o r { i n t nextChar ; Iter () { try { nextChar = i n . r e a d ( ) ; } c a t c h ( IOException ex ) { throw new j a v a . u t i l . NoSuchElementException ( ) ; } } p u b l i c b o o l e a n hasNext ( ) { r e t u r n nextChar >= 0 ; } 35 p u b l i c C h a r a c t e r next ( ) { c h a r r e s u l t = ( c h a r ) nextChar ; try { nextChar = i n . r e a d ( ) ; } c a t c h ( IOException ex ) { throw new j a v a . u t i l . NoSuchElementException ( ) ; } return r e s u l t ; } 36 37 38 39 40 41 42 43 44 45 } 46 23 Kapitel 1 Iterierbare Objekte p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n { f o r ( c h a r c : new R e a d e r I t e r a b l e ( new F i l e R e a d e r ( a r g s [ 0 ] ) ) ) { System . out . p r i n t l n ( c ) ; } } 47 48 49 50 51 52 } Listing 1.46: ReaderIterable.java Aufgabe 1 In dieser Aufgabe geht es darum unterschiedliche Iteratorklassen zu implementieren: a) Lösen Sie die Aufgabe 1 aus Übungsblatt 11. Vergleichen Sie mit der Musterlösung. b) Schreiben Sie eine Klasse ArrayIterator, die im Konstruktor einen Array erhält und bei der Iteratoin über die einzelnen Arrayelemente iteriert. c) Schreiben Sie eine Klasse Words. Sie soll Iterable so implementieren, dass nacheinander die Wörter eines im Konstruktor übergebenen Strings bei der Iteration durchlaufen werden. Wörter werden durch Leerzeichen, Zeilenenden und Tabulatorzeichen getrennt. Es soll keine leeren Wörter geben. d) Schreiben Sie eine generische Klasse IndexIterator. Im Konstruktor soll ein Obejekt des Typs java.util.function.Function übergeben werden. Beim i-ten Aufruf der Methode next() soll der Iterator diese Funktion benutzen, um das nächste Element für den Index i zu erzeugen. e) Benutzen Sie die Klasse aus Teil d) um einen Iterator zu implementieren, der über die Quadratzahlen iteriert. f) Schreiben Sie eine generische Klasse GenerationIterator. Im Konstruktor soll ein Obejekt f des Typs java.util.function.Function und ein Element a des Typs A übergeben werden. Beim Aufruf der Methode next() soll der Iterator die folgende Folge generieren: a, f(a), f(f(a)), f(f(f(a))),.... g) Benutzen Sie die Klasse aus Teil f) um einen Iterator zu implementieren, der über alle ungeraden Zahlen iteriert. h) Ergänzen Sie die Klassen IndexIterator und GeneratorIterator um ein Feld des Typs predicate. Es soll einen überladenen Konstruktor geben, der auch dieses Feld initialisiert. Mit dem Typ des Typs Predicate soll in der Methode next getestet werden, ob der Iterator noch ein weiteres Element generiert. Aufgabe 2 In dieser Aufgabe sollen Sie aus einem oder mehreren übergebenen iterierbaren Objekten neue iterierbare Objekte erzeugen, so wie es die Klasse Append in diesem Kapitel bereits macht. 24 1.4 Die Schnittstelle Iterable a) Schreiben Sie eine generische Klasse Cons, die Iterable implementiert. Es soll folgenden Konstruktor geben: Cons(E head, Iterable tail). Der Iterator zu dieser Klasse soll beim ersten Aufruf von next() das Element head zurück geben und anschließend die Elemente des Iterators von tail. b) Wenn es zwei iterierbare Objekte gibt, dann benötigt man manchmal, gleichzeitig zusammen durch die beiden Objekte zu iterieren. Das Ergebnis ist dann ein iterierbares Objekt, das über Paare von Objekten iteriert. Das erste Paar besteht aus den beiden ersten Objekten der Iterationen, das zweite Paar aus den beiden zweiten usw. Schließlich endet die Iteration, wenn eines der beiden iterierbaren Objekte kein nächstes Element hat. Schreiben Sie zunächst eine generische Klasse Pair, die es ermöglicht Paare von Objekten zu repräsentieren. Schreiben Sie eine generische Klasse PairIterable, die Iterable> implementiert . Sie soll in einem Konstruktor zwei iterierbare Objekte übergeben bekommen: public PairIterable(Iterable it1, Iteratable it2) Beim Iterieren soll dann paarweise durch beide Iterable gelaufen werden. Lösung 1 2 3 4 5 6 7 8 package name . p a n i t z . u t i l ; p u b l i c c l a s s Pair { public A f s t ; p u b l i c B snd ; p u b l i c P a i r (A a , B b ) { fst = a; snd = b ; } 9 @Override p u b l i c S t r i n g t o S t r i n g ( ) { r e t u r n ” ( ” + f s t + ” , ” + snd + ” ) ” ; } 10 11 12 13 } Listing 1.47: Pair.java 1 2 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; 3 4 5 6 p u b l i c c l a s s P a i r I t e r a b l e implements I t e r a b l e > { f i n a l p r i v a t e j a v a . l a n g . I t e r a b l e a s ; f i n a l p r i v a t e j a v a . l a n g . I t e r a b l e bs ; 25 Kapitel 1 Iterierbare Objekte p u b l i c P a i r I t e r a b l e ( j a v a . l a n g . I t e r a b l e as , j a v a . l a n g . I t e r a b l e bs ) { t h i s . as = as ; t h i s . bs = bs ; } 7 8 9 10 11 p u b l i c I t e r a t o r > i t e r a t o r ( ) { r e t u r n new I t e r ( ) ; } 12 13 14 15 p r i v a t e c l a s s I t e r implements I t e r a t o r > { p r i v a t e I t e r a t o r a I t ; p r i v a t e I t e r a t o r b I t ; Iter () { a I t = as . i t e r a t o r ( ) ; b I t = bs . i t e r a t o r ( ) ; } 16 17 18 19 20 21 22 23 p u b l i c b o o l e a n hasNext ( ) { r e t u r n b I t . hasNext ( ) && a I t . hasNext ( ) ; } 24 25 26 27 p u b l i c Pair next ( ) { r e t u r n new Pair <>( a I t . next ( ) , b I t . next ( ) ) ; } 28 29 30 } 31 32 } Listing 1.48: PairIterable.java c) Oft braucht man die Situation, dass man über zwei Dimensionen iteriert. Hierzu hat man für jede Dimension einen eigenen Iterator. Man kann sich das so vorstellen, dass, wenn man an ein übliches Koordinatensystem denkt, die eine Dimension die Punkte auf der x-Achse ist, die zweite Dimension die Punkte auf der y-Achse. Soll über den zweidimensionalen Raum iteriert werden, so wird ein Iterator benötigt, der über alle möglichen (x,y) Paare iteriert. Schreiben Sie eine generische Iteratorklasse TwoDimIterable. Sie soll in einem Konstruktor zwei iterierbare Objekte übergeben bekommen: public TwoDimIterable(Iterable it1, Iterable it2) Lösung Wir geben zwei Lösungen für die Aufgabe. Die erste ist die einfachere, wird aber dem Namen der Klasse nicht wirklich gerecht. Sie funktioniert wie zwei ineinander verschachtelte Schleifen. Die äußere läuft über den einen Iterator, die innere über den anderen. Der Nachteil ist: wenn der innere Iterator nie terminiert, sprich unendlich viele Elemente 26 1.4 Die Schnittstelle Iterable liefert, dann ist nicht mehr gewährleistet, dass jedes Element der Paarmenge irgendwann im Ergebnisiterator erhalten wird. 1 2 3 package name . p a n i t z . u t i l ; import name . p a n i t z . pmt . i t e r a t i o n . I n t e g e r R a n g e ; import j a v a . u t i l . I t e r a t o r ; 4 5 6 7 p u b l i c c l a s s D i a g o n a l I t e r a b l e implements I t e r a b l e >{ j a v a . l a n g . I t e r a b l e a s ; j a v a . l a n g . I t e r a b l e bs ; 8 9 10 11 12 13 14 15 16 17 18 19 p r i v a t e c l a s s I t e r implements I t e r a t o r >{ I t e r a t o r a I t ; I t e r a t o r b I t ; A a = null ; Iter () { a I t = as . i t e r a t o r ( ) ; b I t = bs . i t e r a t o r ( ) ; } p u b l i c b o o l e a n hasNext ( ) { r e t u r n b I t . hasNext ( ) | | a I t . hasNext ( ) ; } 20 p u b l i c Pair next ( ) { i f ( a == n u l l ) a = a I t . next ( ) ; i f ( b I t . hasNext ( ) ) r e t u r n new Pair <>(a , b I t . next ( ) ) ; a = a I t . next ( ) ; b I t = bs . i t e r a t o r ( ) ; 21 22 23 24 25 26 27 r e t u r n next ( ) ; 28 } 29 30 } 31 32 33 34 p u b l i c I t e r a t o r > i t e r a t o r ( ) { r e t u r n new I t e r ( ) ; } 35 36 37 38 39 p u b l i c D i a g o n a l I t e r a b l e ( j a v a . l a n g . I t e r a b l e as , j a v a . l a n g . I t e r a b l e bs ) { t h i s . as = as ; t h i s . bs = bs ; } 40 41 42 43 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { I t e r a b l e a s = new IntRange ( 2 , 2 0 , 2 ) ; I t e r a b l e bs = new IntRange ( 5 1 , 9 9 , 4 ) ; 44 45 f i n a l I t e r a b l e > ps = new D i a g o n a l I t e r a b l e (as , bs ) ; 46 27 Kapitel 1 Iterierbare Objekte f o r ( Pair p : ps ) { System . out . p r i n t l n ( p ) ; } 47 48 49 } 50 51 } Listing 1.49: DiagonalIterable.java Die zweite Umsetzung führt tatsächlich eine Diagonalisierung durch. Hierzu werden zwei interne Hilfslisten verwendet. Diese beinhalten immer die bereits von den beiden Iteratoren gesehenen Elemente. Allerdings sind beide in entgegengesetzter Reihenfolge. Nun kann mit dem PairIterable aus der letzten Aufgabe jeweils der Iterator für eine Diagonale in diesem Zweidimensionalen Raum erzeugt werden. Etwas kompliziert wird die Lösung, weil die beiden Listen, sobald die Iteratoren keine neuen Elemente mehr liefern auf korrekte Weise wieder stückchenweise abgebaut werden müssen. 1 2 3 4 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; import j a v a . u t i l . L i n k e d L i s t ; import j a v a . u t i l . L i s t ; 5 6 7 8 p u b l i c c l a s s Diagonal implements I t e r a b l e > { f i n a l p r i v a t e j a v a . l a n g . I t e r a b l e a s ; f i n a l p r i v a t e j a v a . l a n g . I t e r a b l e bs ; 9 10 11 12 13 p u b l i c D i a g o n a l ( j a v a . l a n g . I t e r a b l e as , j a v a . l a n g . I t e r a b l e bs ) { t h i s . as = as ; t h i s . bs = bs ; } 14 15 16 17 p u b l i c I t e r a t o r > i t e r a t o r ( ) { r e t u r n new I t e r ( ) ; } 18 19 20 21 p r i v a t e c l a s s I t e r implements I t e r a t o r > { p r i v a t e I t e r a t o r a I t = a s . i t e r a t o r ( ) ; p r i v a t e I t e r a t o r b I t = bs . i t e r a t o r ( ) ; 22 23 24 25 26 27 // d i e f o l g e n d e b e i d e n L i s t e n s p e i c h e r n d i e b e r e i t s ges eh en d e n i t e r i e r t e n // Element // S i e s o l l e n immer zusammen e i n e D i a g o n a l e d a r s t e l l e n p r i v a t e L i s t xs = new L i n k e d L i s t <>() ; p r i v a t e L i s t ys = new L i n k e d L i s t <>() ; 28 29 30 28 // d e r Paar−I t e r a t o r g e h t immer u e b e r e i n e D i a g o n a l e p r i v a t e I t e r a t o r > ps = new P a i r I t e r a b l e <>(xs , ys ) . i t e r a t o r ( ) ; 1.4 Die Schnittstelle Iterable 31 @Override p u b l i c b o o l e a n hasNext ( ) { r e t u r n ps . hasNext ( ) | | ( a I t . hasNext ( ) && b I t . hasNext ( ) ) | | ( xs . s i z e ( ) >= 1 && b I t . hasNext ( ) ) | | ( ys . s i z e ( ) >= 1 && a I t . hasNext ( ) ) | | ( xs . s i z e ( ) > 1 && xs . s i z e ( ) > 1 ) ; } 32 33 34 35 36 37 38 39 @Override p u b l i c Pair next ( ) { i f ( ! ps . hasNext ( ) ) { b o o l e a n aitHadNext = a I t . hasNext ( ) ; 40 41 42 43 44 // das n a e c h s t e x wird h i n t e n angehaengt i f ( a I t . hasNext ( ) ) { xs . add ( a I t . next ( ) ) ; } // wenn k e i n ne u e s y mehr kommt , wird x l i s t e vorne um e i n // Element g e k u e r z t i f ( ! b I t . hasNext ( ) ) { xs = xs . s u b L i s t ( 1 , xs . s i z e ( ) ) ; } 45 46 47 48 49 50 51 52 53 54 // das n a e c h s t e y wird vorne angehaengt i f ( b I t . hasNext ( ) ) { ys . add ( 0 , b I t . next ( ) ) ; } // wenn k e i n ne u e s x mehr kam , wird y l i s t e h i n t e n um 55 56 57 58 59 ein // Element g e k u e r z t 60 61 i f ( ! aitHadNext ) { ys = ys . s u b L i s t ( 0 , ys . s i z e ( ) − 1 ) ; } 62 63 64 65 // D i a g o n a l e wurde a u f g e b a u t und kann j e t z t i t e r i e r t werden ps = new P a i r I t e r a b l e <>(xs , ys ) . i t e r a t o r ( ) ; } r e t u r n ps . next ( ) ; } 66 67 68 69 70 71 } 72 73 74 75 76 77 78 79 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { L i s t xs = new L i n k e d L i s t <>() ; xs . add ( ” x1 ” ) ; xs . add ( ” x2 ” ) ; xs . add ( ” x3 ” ) ; xs . add ( ” x4 ” ) ; L i s t ys = new L i n k e d L i s t <>() ; 29 Kapitel 1 Iterierbare Objekte ys . add ( ” y1 ” ) ; ys . add ( ” y2 ” ) ; ys . add ( ” y3 ” ) ; ys . add ( ” y4 ” ) ; ys . add ( ” y5 ” ) ; new Diagonal <>(xs , ys ) . f o r E a c h ( p −>System . out . p r i n t l n ( p ) ) ; 80 81 82 83 84 85 } 86 87 } Listing 1.50: Diagonal.java d) Schreiben Sie eine generische Iterableklasse Interleave. Sie soll in einem Konstruktor zwei iterierbare Objekte übergeben bekommen. Die resultierende Iteration, soll abwechselnd aus den beiden Iterable, die Elemente beziehen, bis einer der beiden Iteratoren kein weiteres Element liefert. 1.5 Funktionen als Parameter Mit der Version 1.8 von Java sollte die Schnittstelle Iterable zunächst eine gewaltige Aufwertung erhalten. Dieses führte schließlich dazu, dass für die neuen Funktionalitäten, die in die Schnittstelle eingebaut wurden, eine komplett neue Schnittstelle entwickelt wurde, die sich nun in dem Paket java.util.stream befindet. Es ist die Schnittstelle Stream, die in Zukunft eine wesentliche Erweiterung des Konzeptes für Iteratoren ausdrücken wird. Da diese zum jetzigen Zeitpunkt noch nicht endgültig definiert ist, werden wir sie in diesem Semester noch nicht betrachten. Dafür betrachten wir mögliche Erweiterungen der Schnittstelle Iterable. Bisher hatte die Schnittstelle Iterable nur die einzige Methode iterator(), mit der ein Iterator-Objekt erzeugt wurde, das zum einmaligen Iterieren durch die Elemente eines Objekts benutzt werden konnte. Zwei neue Konzepte bringt Java 1.8 mit, die es ermöglichen, die Schnittstelle Iterable um wesentliche Funktionalität zu erweitern und ihre eine noch zentralere Rolle zukommen zu lassen. • Mit Java 1.8 können Schnittstellen, wie bisher nur von abstrakten Klassen her bekannt, auch Methoden bereits konkret implementieren, wobei sie die abstrakten Methoden bereits benutzen können. • Mit Java 1.8 werden sogenannte Lambda-Ausdrücke, die auch als Closures bezeichnet werden, sowie auch Methodenobjekte der Sprache Java hinzugefügt. Mit diesen neuen Sprachkonzepten wird eine bisher in Java nur schwer realisierbare Programmiermethodik möglich. Die sogenannte Programmierung höherer Ordnung. 30 1.5 Funktionen als Parameter Dabei werden Methoden oder auch Codeblöcke als Parameter anderen Methoden übergeben. Dieses ermöglicht eine wesentlich stärkere Abstraktion in der Parameterisierung des Codes. Es kann jetzt über eine ganze Befehlsfolge auf einfache Weise abstrahiert werden. Die Schnittstelle Iterable hat weiterhin nur einen abstrakte Methode, die Methode iterator(), die wir bereits kennen. Zusätzlich sind eine ganze Menge weiterer Methoden implementiert. Bei diesen handelt es sich zum großen Teil um Methoden höherer Ordnung. Um die Beispiele dieses Kapitels zu übersetzen, wird ein Compiler benötigt, der bereits die entsprechenden Sprachkonstrukte für Java 1.8 unterstützt. Er kann auf der Webseite des Project Lambda (http://openjdk.java.net/projects/lambda) herunter geladen werden. 1.5.1 Schleifen als Methoden Die erste neue Methode höherer Ordnung der Schnittstelle Iterable, die wir betrachten, ist die Methode forEach. Sie hat ein Argument. Dieses ist ein Parameter vom neuen Typ Block. Es handelt sich dabei um einen belieben Code-Block, der abhängig ist von einer Variablen. Hierfür gibt es eine neue Syntax. Es wird die Variable (eine in diesem Kontext neu deklarierte Variable) in runde Klammern geschrieben. Anschließend folgt ein aus einem Minuszeichen und dem Größerzeichen gebildeter Pfeil. Danach kommt in geschweifte Klammern ein beliebiger Code-Block. Damit ist es möglich, die for-Schleife komplett als Methodenaufruf darzustellen: 1 2 3 4 package name . p a n i t z . pmt . i t e r a t i o n ; p u b l i c c l a s s ForEachExample { p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { I n t e g e r R a n g e i s = new I n t e g e r R a n g e ( 0 , 1 0 , 1 ) ; 5 i s . f o r E a c h ( ( x ) −> { System . out . p r i n t l n ( x ) ; } ) ; 6 } 7 8 } Listing 1.51: ForEachExample.java Die Zeile is.forEach( (x) -> {System.out.println(x);}) ist wie folgt zu lesen: Für jedes Element x im iterierbaren Objekt is führe den Code-Block aus, der aus der einzigen Anweisung println besteht. 1.5.2 Standard- (default) Methoden in Schnittstellen Mit Java 1.8 ist es jetzt möglich, ähnlich wie in abstrakten Klassen bestimmte Methoden bereits auszuimplementieren und dabei bereits auf die noch abstrakten Methoden zuzugreifen. Ein Beispiel hierfür haben wir bereits gesehen. In der Schnittstelle 31 Kapitel 1 Iterierbare Objekte Iterable gibt es nun die Methode forEach. Diese ist bereits in der Schnittstelle konkret implementiert. Beim Implementieren der Schnittstelle brauchten wir diese nicht selbst zu implementieren. Wir haben sie quasi geschenkt bekommen. Mit Java 1.8 wird auch in der Schnittstelle Iterator eine Standardmethode eingeführt. Die bisher abstrakte aber in der Dokumentation als optional beschriebene Methode remove hat von nun an eine Standardimplementierung und braucht beim Implementieren der Schnittstelle nicht mehr selbst implementiert werden. Wie man sieht, könnte man mit dieser Methode fast auf die Schleifen in Java verzichten. Tatsächlich gibt Es Programmiersprachen, die diesen Weg gegangen sind und keine speziellen Schleifenkonstrukte kennen. 1.5.3 Iterable als verzögerte Listen Man kann mit den default-Methoden jetzt soweit gehen, dass man gleich die Schnittstelle Iterable soweit aufmotzt, dass sie bereits eine einfach verkettete Liste darstellt. Hierzu braucht sie im Prinzip fünf Methoden: • eine Testmethode empty(), die testet, ob die Liste leer ist. • eine Methode head(), die das erste Element zurück gibt. • eine Methode tail(), die die Liste ohne das erste Element zurück gibt. • eine Methode nil() zum Erzeugen einer leeren Liste. • eine Methode cons(x,xs), die eine neue Liste erzeugt, bestehend aus einem neuem ersten Element und einer Restliste. Wir können tatsächlich eine eigene Schnittstelle Iterable schreiben, die diese fünf Methoden hat. Hierzu schreiben wir eine Unterschnittstelle von Iterable mit den entsprechenden dafault-Methoden: 1 2 3 4 5 package name . p a n i t z . u t i l ; import j a v a . u t i l . I t e r a t o r ; import j a v a . u t i l . f u n c t i o n . * ; p u b l i c i n t e r f a c e I t e r a b l e e x t e n d s j a v a . l a n g . I t e r a b l e { p u b l i c I t e r a t o r i t e r a t o r ( ) ; Listing 1.52: Iterable.java Die Testmethode, ob es sich um eine leere Liste handelt, lässt sich einfach implementieren. Man lässt sich einen Iterator geben und fragt diesen, ob er ein nächstes Element hat. p u b l i c d e f a u l t b o o l e a n empty ( ) { r e t u r n ! i t e r a t o r ( ) . hasNext ( ) ; } 6 7 8 Listing 1.53: Iterable.java 32 1.5 Funktionen als Parameter Die Methode head ist einfach zu implementieren. Es muss das Iterable-Objekt nach seinem Iterator gefragt werden und von diesem das erste Element zurück gegeben werden. 9 10 11 p u b l i c d e f a u l t A head ( ) { r e t u r n i t e r a t o r ( ) . next ( ) ; } Listing 1.54: Iterable.java Auch die Methode tail ist einfach zu implementieren. Es wird zunächst der Iterator erfragt. Dieser wird durch einmaliges Aufrufen von next() auf das zweite Element geschaltet. Nun ist es der Iterator für das neue Iterable. 12 13 14 15 16 17 18 p u b l i c d e f a u l t I t e r a b l e t a i l ( ) { r e t u r n ( )−>{ I t e r a t o r r e s u l t = i t e r a t o r ( ) ; r e s u l t . next ( ) ; return r e s u l t ; }; } Listing 1.55: Iterable.java Aufmerksamen Beobachtern mag aufgefallen sein, dass sich mit den Methoden empty und tail bereits viele typische Methoden für Listenstrukturen umsetzen lassen. So zum Beispiel die Berechnung der Länge: 19 20 21 public default int length () { r e t u r n empty ( ) ? 0 : 1+ t a i l ( ) . l e n g t h ( ) ; } Listing 1.56: Iterable.java cons und nil für Iterables Wir brauchen die Methode nil() die uns eine Iterable-Objekt erzeugt, dessen Iterator über kein einziges Element iteriert. Es ist also ein leeres Iterable-Objekt. Deshalb gibt die Methode hasNext immer false zurück.: 22 23 24 25 26 27 p u b l i c s t a t i c I t e r a b l e n i l ( ) { r e t u r n ( )−>{ r e t u r n new I t e r a t o r () { p u b l i c b o o l e a n hasNext ( ) { r e t u r n f a l s e ; } p u b l i c A next ( ) { throw new j a v a . u t i l . NoSuchElementException ( ” next f o r empty iterator ”) ; 33 Kapitel 1 Iterierbare Objekte } 28 }; 29 }; 30 } 31 Listing 1.57: Iterable.java Als Objektmethode schreiben wir als nächstes die Methode cons, diese erhält ein Element, welches bei der resultierenden Iteration als erstes Element zurück gegeben werden soll. Es wird also an den Iterationsbereich vorne noch ein Element angehängt. p u b l i c d e f a u l t I t e r a b l e c o n s ( f i n a l A hd ) { f i n a l I t e r a b l e t l = t h i s ; r e t u r n ( ) −> { f i n a l j a v a . u t i l . I t e r a t o r i t = t l . i t e r a t o r ( ) ; f i n a l A f i r s t = hd ; r e t u r n new I t e r a t o r () { boolean f i r s t C a l l = true ; p u b l i c b o o l e a n hasNext ( ) { r e t u r n f i r s t C a l l | | i t . hasNext ( ) ; } p u b l i c A next ( ) { i f ( firstCall ){ firstCall = false ; return f i r s t ; } else { r e t u r n i t . next ( ) ; } } }; }; } 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 Listing 1.58: Iterable.java Als eine statische Methode wird noch eine Variante dieser Methode implementiert. Diese Variante erhält das erste Element für die Iteration, sowie ein iterierbares Objekt für die übrigen Elemente. Dieses zweite iterierbare Objekt wird allerdings nicht direkt übergeben, sondern über ein Supplier-Objekt. Dieses hat genau eine Methode namens get, mit der das Objekt erfragt werden kann, das der Supplier liefert. p u b l i c s t a t i c I t e r a b l e c o n s (A hd , f i n a l j a v a . u t i l . f u n c t i o n . S u p p l i e r > t l ) { r e t u r n ( )−>{ f i n a l A f i r s t = hd ; r e t u r n new I t e r a t o r () { j a v a . u t i l . I t e r a t o r i t = n u l l ; boolean f i r s t C a l l = true ; 51 52 53 54 55 56 57 p u b l i c b o o l e a n hasNext ( ) { 58 34 1.5 Funktionen als Parameter i f ( f i r s t C a l l ) return true ; i f ( i t==n u l l ) i t=t l . g e t ( ) . i t e r a t o r ( ) ; r e t u r n i t . hasNext ( ) ; 59 60 61 } 62 63 p u b l i c A next ( ) { i f ( firstCall ){ firstCall = false ; return f i r s t ; }else { i f ( i t==n u l l ) i t=t l . g e t ( ) . i t e r a t o r ( ) ; r e t u r n i t . next ( ) ; } } 64 65 66 67 68 69 70 71 72 }; 73 }; 74 75 } Listing 1.59: Iterable.java Filter für die Iteration Mit den oben geschriebenen Methoden head, tail und cons lassen sich jetzt auf einfache Weise weitere sinnvolle Methoden für iterierbare Objekte schreiben. Zum Beispiel ein einfacher Filter. Es wird hierzu ein Prädikat übergeben, dass für ein einzelnen Element zu wahr oder falsch auswertet. Es soll ein neues iterierbares Objekt erzeugt werden, das nur noch als den Objekten des ursprünglichen existiert, für die das Prädikat zu wahr auswertet. Es gibt drei Fälle: • für ein leeres iterierbares Objekt ist auch das Ergebnis ein solche leeres Objekt. • dann ist zu testen, ob das erste Element das Prädikat erfüllt. Wenn ja, dann wird dieses als erstes Element des Ergebnisses mit cons auf den rekursiven Aufruf auf den Reste zugefügt. • ansonsten wird die Methode nur rekursiv auf die übrigen Elemente des Iterationsbereiches angewendet. 76 77 78 79 80 p u b l i c d e f a u l t I t e r a b l e f i l t e r ( P r e d i c a t e p ) { i f ( empty ( ) ) r e t u r n t h i s ; i f ( p . t e s t ( head ( ) ) ) r e t u r n c o n s ( head ( ) , ( )−> t a i l ( ) . f i l t e r ( p ) ) ; return t a i l () . f i l t e r (p) ; } Listing 1.60: Iterable.java 35 Kapitel 1 Iterierbare Objekte Elemente Abbilden Eine einfache und vielfach in Sprachen, die Lambda-Ausdrücke unterstützen, verwendete Funktion, generiert aus einem Iterationsbereich mit Elementen eines bestimmten Typs einen neuen Iterationsbereich ,der entsteht, indem eine Funktion auf jedes Element angewendet wird. Es ist also wieder eine Form, der Anwendung für alle Elemente. Hier wird für jedes Element in ein neues Element mit Hilfe einer übergebenen Funktion generiert. p u b l i c d e f a u l t I t e r a b l e map( Function f ) { i f ( empty ( ) ) r e t u r n n i l ( ) ; r e t u r n c o n s ( f . apply ( head ( ) ) , ( )−> t a i l ( ) . map( f ) ) ; } 81 82 83 84 Listing 1.61: Iterable.java Faltungen Eine mächtige Funktion für Iterationsbereiche sind sogenannte Faltungen (in vielen Bibliotheken heißen die entsprechenden Funtionen fold). Bei Faltungen geht es darum, die Elemente alle mit einer Operation zu verknüpfen. Hierzu wird eine Operation benötigt, die zwei Elemente verknüpfen kann. Zusätzlich wird ein Startwert erwartet, der insbesondere als Ergebnis benutzt wird, wenn keine Elemente in der Iteration sind. Für eine Funktion ⊗ als Operation in Infix-Schreibweise sei die Faltung wie folgt definiert: reduce([x1 , x2 , . . . , xn ], start, ⊗) = start ⊗ x1 ⊗ x2 ⊗ · · · ⊗ xn Die Implementierung dieser Funktion lässt sich wunderbar per Iteration machen. Dazu werden die Elemente in einer Schleife nacheinander mit dem Startwert verknüpft. p u b l i c d e f a u l t B r e d u c e (B s t a r t , BiFunction op ) { f o r (A x : t h i s ) s t a r t = op . apply ( s t a r t , x ) ; return start ; } 85 86 87 88 Listing 1.62: Iterable.java So kurz und einfach diese Funktion wirkt, umso mächtiger ist sie, was auf den ersten Blick nicht so ersichtlich ist. Ein paar Beispiele für weitere Funktionen, die sich mit reduce umsetzen lassen. Im Prinzip ist es die Verallgemeinerung einer Iteration, bei der nacheinander die Elemente der Iteration auf ein Ergebnis akkumuliert werden. Dabei ist der Parameter start jeweils die Initialisierung der Ergebnisvariablen. 36 1.5 Funktionen als Parameter So lässt sich zum Beispiel eine Funktion schreiben, die testet, ob eines der Elemente im Iterationsbereich ein bestimmtes Prädikat erfüllt. Diese Funktion entspricht dem logischen Existenzquantor. Der Startwert ist in diesem Fall false. Es wird für jedes Element getestet, ob es das Prädikat erfüllt und schließlich die Teilergebnisse mit Oder verknüpft. 89 90 91 p u b l i c d e f a u l t b o o l e a n e x i s t s ( P r e d i c a t e p ) { r e t u r n r e d u c e ( f a l s e , ( x , y )−> x | | p . t e s t ( y ) ) ; } Listing 1.63: Iterable.java Analog funktioniert die Funktion, die testet, ob ein Prädikat für alle Elemente des Iterationsbereichs zutrifft. Diese Funktion entspricht dem logischen Allquantor. 92 93 94 p u b l i c d e f a u l t b o o l e a n f o r A l l ( P r e d i c a t e p ) { r e t u r n r e d u c e ( t r u e , ( x , y )−> x && p . t e s t ( y ) ) ; } Listing 1.64: Iterable.java So lässt sich wiederum mit der Funktion exists schnell testen, ob ein bestimmtes Element in einem Iterationsbereich enthalten ist. 95 96 97 p u b l i c d e f a u l t b o o l e a n c o n t a i n s (A e l ) { r e t u r n e x i s t s ( x −> x . e q u a l s ( e l ) ) ; } Listing 1.65: Iterable.java Es lassen sich auch wie bei einer Methode toString die Stringdarstellungen aller Elemente in einem String zusammenfügen: 98 99 100 public default String stringRepresentation () { r e t u r n r e d u c e ( ” ” , ( x , y ) −> x+” ”+y ) ; } Listing 1.66: Iterable.java Eine entsprechende Funktion, die ein Objekt der Klasse StringBuffer nutzt, um die Elemente zu einem Gesamtstring zu akkumulieren, lässt sich auch mit Hilfe von reduce formulieren: 101 102 103 104 105 p u b l i c d e f a u l t S t r i n g show ( ) { r e t u r n r e d u c e ( new S t r i n g B u f f e r ( ) , ( buf , y ) −> { buf . append ( ” ” ) ; buf . append ( y ) ; 37 Kapitel 1 Iterierbare Objekte r e t u r n buf ; }) . t o S t r i n g ( ) ; 106 107 } 108 Listing 1.67: Iterable.java Auch die Längenberechnung lässt sich mittels reduce formulieren. Als Operation wird immer die Konstante 1 hinzuaddiert: public default int length2 () { r e t u r n r e d u c e ( 0 , ( r e s u l t , x ) −> r e s u l t +1) ; } 109 110 111 Listing 1.68: Iterable.java Es lässt sich sogar die Funktion filter aus diesem Abschnitt nun komplett mit Hilfe von reduce darstellen: p u b l i c d e f a u l t I t e r a b l e f i l t e r 2 ( P r e d i c a t e p ) { r e t u r n ( ) −> ( r e d u c e ( new j a v a . u t i l . A r r a y L i s t () , ( r e s u l t , y ) −> { i f ( p . t e s t ( y ) ) r e s u l t . add ( y ) ; return r e s u l t ; }) . i t e r a t o r ( ) ) ; } 112 113 114 115 116 117 118 Listing 1.69: Iterable.java Gleiches gilt auch für die Funktion map, die sich mit Hilfe von reduce formulieren lässt. p u b l i c d e f a u l t I t e r a b l e map2 ( Function f ) { r e t u r n ( ) −> ( r e d u c e ( new j a v a . u t i l . A r r a y L i s t () , ( r e s u l t , y ) −> { r e s u l t . add ( f . apply ( y ) ) ; return r e s u l t ; }) . i t e r a t o r ( ) ) ; } 119 120 121 122 123 124 125 Listing 1.70: Iterable.java Ebenso lässt sich für einen größer Vergleich mittels eines Comparator-Objekts mit reduce das Maximum der Elemente eines Iterationsbereichs ermitteln. p u b l i c d e f a u l t A max( j a v a . u t i l . Comparator comp ) { return reduce ( null , ( r e s u l t , y ) −> { i f ( n u l l==r e s u l t ) r e t u r n y ; i f ( comp . compare ( r e s u l t , y ) > 0 ) r e t u r n r e s u l t ; 126 127 128 129 130 38 1.5 Funktionen als Parameter return y ; 131 } 132 ); 133 } 134 135 } Listing 1.71: Iterable.java Sieb des Eratosthenes Mit der obigen Filter-Funktion lässt sich eine iterierbares Objekt schreiben, dass über alle Primzahlen iteriert. Hierzu kann das bekannte Verfahren des Siebs des Eratosthenes angewendet werden. Gestartet wird mit der Folge von Zahlen beginnend von 2. 1 2 3 4 5 6 7 package name . p a n i t z . pmt . u t i l ; import name . p a n i t z . u t i l . I t e r a b l e ; p u b l i c c l a s s PrimesExample { p u b l i c s t a t i c I t e r a b l e s i e v e ( f i n a l I t e r a b l e i t ) { f i n a l i n t f i r s t = i t . head ( ) ; r e t u r n I t e r a b l e . c o n s ( f i r s t , ( )−>s i e v e ( i t . f i l t e r ( ( x )−> x%f i r s t != 0 ))); } 8 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { I t e r a b l e i s = ( )−>new I n t e g e r R a n g e ( 2 ) . i t e r a t o r ( ) ; I t e r a b l e pr im e s = s i e v e ( i s ) ; p r i m e s . f o r E a c h ( x−>System . out . p r i n t l n ( x ) ) ; } 9 10 11 12 13 14 } Listing 1.72: PrimesExample.java Aufgabe 1 Schreiben Sie in dieser Aufgabe eine Klasse, die eine auf einem Array als Speicher benutzende generische Liste realisiert. (Eine solche Klasse wurde bereits im ersten Semester entwickelt.) Sie soll folgende Eigenschaften haben: a) Es soll das Interface name.panitz.util.Iterable implementiert werden. b) Es soll eine Methode add zum Hinzufügen neuer Elemente geben. c) Es soll zwei Konstruktoren geben. Einen Standardkonstruktor ohne Parameter und einen Konstruktor mit einer variablen Parameteranzahl. Im zweiten Fall sollen die Parameter die Elemente der Liste werden. d) Die Standardmethode length soll überschrieben werden. Warum ist dieses sinnvoller als auf die bereits in name.panitz.util.Iterable implementierte Standardmethode zu vertrauen? 39 Kapitel 1 Iterierbare Objekte Schreiben Sie umfangreiche Tests für alle Methoden. Aufgabe 2 Schreiben Sie für die Schnittstelle name.panitz.util.Iterable weitere Standardmethoden: a) Eine Methode zum Anhängen eines weiteren Iterable-Objekts: public default Iterable append(java.lang.Iterable that) Sie dürfen dabei die Klasse Append des ersten Übungsblattes verwenden. b) Schreiben Sie unter Zuhilfenahme der Klasse PairIterable des letzten Übungsblattes folgende Methode, die das Iterable-Objekt paarweise mit einem zweiten Iterable-Objekt verbindet: public default Iterable> zip(java.lang.Iterable that) c) Schreiben Sie jetzt unter Zuhilfenahme der Klasse TwoDimIterable folgende Methode, die alle Paare aus den beiden Iterable-Objekten bereit stellt: public default Iterable> cross(java.lang.Iterable that) d) Schreiben Sie eine Standardmethode für die Stringdarstellung eines Iterable-Objekts. public default String asString() Was passiert, wenn Sie versuchen als Standardmethode die Methode toString zu überschreiben. Testen Sie mit Hilfe der Klasse AList aus der ersten Aufgabe alle Standardmethoden der Klasse Iterable. 40 Kapitel 2 Hierarchische Strukturen Von zentraler Bedeutung in der Softwareentwicklung ist es, Daten auf eine hierarchische Art und Weise zu strukturieren. Solche hierarchisch strukturierten Daten sind als Bäume bekannt. Sie sind in der Softwareentwicklung von so zentraler Bedeutung, dass sich verschiedene Standards durchgesetzt haben, solche Daten in textueller Form zu serialisieren. Zum einen in Form von XML-Dokumenten, zum anderen aber auch z.B. durch das JASON-Format. In diesem Kapitel werden wir uns mit hierarchischen Daten zunächst in allgemeiner Form und anschließend in Form von XML-Dokumenten beschäftigen. 2.1 Einfach verkettete Listen Eine der häufigsten Datenstrukturen in der Programmierung sind Sammlungstypen. In fast jedem nichttrivialen Programm wird es Punkte geben, an denen eine Sammlung mehrerer Daten gleichen Typs anzulegen sind. Eine der einfachsten Strukturen, um Sammlungen anzulegen, sind Listen. Da Sammlungstypen oft gebraucht werden, stellt Java entsprechende Klassen als Standardklassen zur Verfügung. Bevor wir uns aber diesen bereits vorhandenen Klassen zuwenden, wollen wir in diesem Kapitel Listen selbst spezifizieren und programmieren. 2.1.1 Formale Definition Wir werden Listen als abstrakten Datentyp formal spezifizieren. Ein abstrakter Datentyp (ADT) wird spezifiziert über eine endliche Menge von Methoden. Hierzu wird spezifiziert, auf welche Weise Daten eines ADT konstruiert werden können. Dazu werden entsprechende Konstruktormethoden spezifiziert. Dann wird eine Menge von Funktionen definiert, die wieder Teile aus den konstruierten Daten selektieren können. Schließlich werden noch Testmethoden spezifiziert, die angeben, mit welchem Konstruktor ein Datenobjekt erzeugt wurde. Der Zusammenhang zwischen Konstruktoren und Selektoren sowie zwischen den Konstruktoren und den Testmethoden wird in Form von Gleichungen spezifiziert. 41 Kapitel 2 Hierarchische Strukturen Der Trick, der angewendet wird, um abstrakte Datentypen wie Listen zu spezifizieren, ist die Rekursion. Das Hinzufügen eines weiteren Elements zu einer Liste wird dabei als das Konstruieren einer neuen Liste aus der Ursprungsliste und einem weiteren Element betrachtet. Mit dieser Betrachtungsweise haben Listen eine rekursive Struktur: eine Liste besteht aus dem zuletzt vorne angehängten neuen Element, dem sogenannten Kopf der Liste, und aus der alten Teilliste, an die dieses Element angehängt wurde, dem sogenannten Schwanz der Liste. Wie bei jeder rekursiven Struktur bedarf es eines Anfangs der Definition. Im Falle von Listen wird dieses durch die Konstruktion einer leeren Liste spezifiziert.1 Listenkonstruktoren Abstrakte Datentypen wie Listen lassen sich durch ihre Konstruktoren spezifizieren. Die Konstruktoren geben an, wie Daten des entsprechenden Typs konstruiert werden können. In dem Fall von Listen bedarf es nach den obigen Überlegungen zweier Konstruktoren: • einem Konstruktor für neue Listen, die noch leer sind. • einem Konstruktor, der aus einem Element und einer bereits bestehenden Liste eine neue Liste konstruiert, indem an die Ursprungsliste das Element angehängt wird. Wir benutzen in der Spezifikation eine mathematische Notation der Typen von Konstruktoren.2 Dem Namen des Konstruktors folgt dabei mit einem Doppelpunkt abgetrennt der Typ. Der Ergebnistyp wird von den Parametertypen mit einem Pfeil getrennt. Somit lassen sich die Typen der zwei Konstruktoren für Listen wie folgt spezifizieren: • Empty: () → List • Cons: (Object,List) →List Listenselektoren Die Selektoren können wieder auf die einzelnen Bestandteile der Konstruktion zurückgreifen. Der Konstruktor Cons hat zwei Parameter. Für Cons-Listen werden zwei Selektoren spezifiziert, die jeweils einen dieser beiden Parameter wieder aus der Liste selektieren. Die Namen dieser beiden Selektoren sind traditioneller Weise head und tail. • head: (List) →Object 1 Man vergleiche es mit der Definition der natürlichen Zahlen: die 0 entspricht der leeren Liste, der Schritt von n nach n + 1 dem Hinzufügen eines neuen Elements zu einer Liste. 2 Entgegen der Notation in Java, in der der Rückgabetyp kurioser Weise vor den Namen der Methode geschrieben wird. 42 2.1 Einfach verkettete Listen • tail: (List) →List Der funktionale Zusammenhang von Selektoren und Konstruktoren läßt sich durch folgende Gleichungen spezifizieren: head(Cons(x, xs)) = x tail(Cons(x, xs)) = xs Testmethoden für Listen Um für Listen Algorithmen umzusetzen, ist es notwendig, unterscheiden zu können, welche Art der beiden Listen vorliegt: die leere Liste oder eine Cons-Liste. Hierzu bedarf es noch einer Testmethode, die mit einem bool’schen Wert als Ergebnis angibt, ob es sich bei der Eingabeliste um die leere Liste handelte oder nicht. Wir wollen diese Testmethode isEmpty nennen. Sie hat folgenden Typ: • isEmpty: List→ boolean Das funktionale Verhalten der Testmethode läßt sich durch folgende zwei Gleichungen spezifizieren: isEmpty(Empty()) = true isEmpty(Cons(x, xs)) = f alse Somit ist alles spezifiziert, was eine Listenstruktur ausmacht. Listen können konstruiert werden, die Bestandteile einer Liste wieder einzeln selektiert und Listen können nach der Art ihrer Konstruktion unterschieden werden. 2.1.2 Implementierung 1 2 3 4 package name . p a n i t z . u t i l ; p u b l i c c l a s s LiLi implements I t e r a b l e { p r i v a t e f i n a l A hd ; p r i v a t e f i n a l LiLi t l ; 5 6 7 8 9 p u b l i c L i L i (A hd , LiLi t l ) { t h i s . hd = hd ; this . tl = tl ; } 10 11 12 13 14 public LiLi () { t h i s . hd = n u l l ; this . tl = null ; } 15 43 Kapitel 2 Hierarchische Strukturen p u b l i c b o o l e a n isEmpty ( ) { r e t u r n t l==n u l l && hd==n u l l ; } 16 17 18 19 p u b l i c A getHead ( ) { r e t u r n hd ; } p u b l i c LiLi g e t T a i l ( ) { return t l ; } 20 21 22 23 24 25 Listing 2.1: LiLi.java Ein Iterator für Listen Für die so definierten Listen können wir jetzt sofort das gelernte aus dem letzten Kapitel anwenden, indem wir einen Iterator für Listen schreiben. Listen sind natürlich die Paradeanwendung für iterierbare Objekte. p u b l i c j a v a . u t i l . I t e r a t o r i t e r a t o r ( ) { r e t u r n new I t ( t h i s ) ; } 26 27 28 29 p r i v a t e c l a s s I t implements j a v a . u t i l . I t e r a t o r { LiLi t h e L i s t ; I t ( LiLi l i ) { t h e L i s t = l i ; } p u b l i c b o o l e a n hasNext ( ) { r e t u r n ! t h e L i s t . isEmpty ( ) ; } p u b l i c A next ( ) { A r e s u l t = t h e L i s t . getHead ( ) ; theList = theList . getTail () ; return r e s u l t ; } } 30 31 32 33 34 35 36 37 38 39 40 41 Listing 2.2: LiLi.java 2.1.3 Mathematische Definition Betrachten wir jetzt wieder die Listen von der mathematischen Seite. Die obige Javaklasse definiert implizit eine induktive Struktur, indem gesagt wird, dass man eine leere Liste konstruieren kann und aus einer Liste und einem weiteren Element eine neue Liste konstruieren kann. Etwas formaler und nicht programmiersprachlich ließen sich Listen wie folgt definieren: Definition 2.1.1 Sei eine Menge E der Elemente gegeben. Die Menge der Listen über E im Zeichen ListE sei dann die kleinste Menge, für die gilt: 44 2.1 Einfach verkettete Listen • Die leere Liste ist eine Liste: Nil() ∈ ListE . • Sei e ∈ E und sei es ∈ ListE . Dann ist auch Cons(e, es) ∈ ListE . Induktive Strukturen Daten sollen nach Möglichkeit dynamisch wachsen können, d.h. potentiell beliebig groß werden können. Um einen Datentyp zu definieren, dessen Elemente potentiell beliebig groß werden können, bedient man sich einer strukturell induktiven Definition. Mit den einfach verketteten Listen haben wir eine induktive Struktur definiert. Dieses Verfahren, Mengen mit zu definieren, ist aus der Mathematik bekannt. Dort ist die Menge der natürlichen Zahlen ein prominentes Beispiel einer induktiv definierten Menge. Die Zahl 0 ist Element der Menge der natürlichen Zahlen. Bei unseren Listen entspricht dieses der leeren Liste. Zusätzlich gilt, wenn eine Zahl n in der Menge der natürlichen Zahlen ist, dass ist auch eine Zahl n + 1 in der Menge der natürlichen Zahlen enthalten. Entsprechend können wir für jede Liste xs eine Liste, die ein Element mehr enthält, erzeugen. Um in der Mathematik eine Eigenschaft für alle Elemente der Menge der natürlichen Zahlen zu beweisen, gibt es ein spezielles Beweisverfahren, dass sich an der strukturellen Definition orientiert: die vollständige Induktion. Diese besteht aus drei Schritte: • Induktions-Anfang: zeige, dass die Eigenschaft für die Zahl 0 gilt. • Induktions-Vorraussetzung: nimm an, dass die Eigenschaft bereits für alle Zahlen kleiner gleich einer bestimmten Zahl n korrekt bewiesen ist. • Induktions-Schluss: zeige, dass die Annahme dann auch für die Zahl n + 1 gilt. Als Beispiel können wir die Gaußsche Summenformel beweisen, die behauptet: n ∑ i= i=0 n ∗ (n + 1) 2 • Induktions-Anfang: 0 = (0 ∗ 1)/2 ist korrekt. • Induktions-Vorraussetzung: n ∑ i=0 i= n ∗ (n + 1) 2 ist beweisen für alle Zahlen kleiner oder gleich eines bestimmten n . 45 Kapitel 2 Hierarchische Strukturen • Induktions-Schluss: n+1 ∑ i= i=0 (n + 1) + n ∑ i i=0 = = = = = n ∗ (n + 1) (n + 1) + 2 2 ∗ (n + 1) n ∗ (n + 1) + 2 2 2 ∗ (n + 1) + n ∗ (n + 1) 2 (n + 2) ∗ (n + 1) 2 (n + 1) ∗ (n + 2) 2 Rekursive Algorithmen Ebenso wie der Mathematiker bei einem Beweis per vollständiger Induktion vorgeht, können wir Algorithmen für die induktiv definierte Struktur unserer Listen definieren. Auch hier gibt es die drei Schritte: • Induktionsanfang: das Ergebnis der zu entwickelnden Funktion wird für die leere Liste definiert. • Induktionsvorraussetzung. wir nehmen an, dass die Funktion bereits für kürzere Listen funktioniert. Insbesondere für die um ein Element kürzere Liste, nämlich für die Liste, die wir mit getTail() erhalten. • Induktionsschritt: aus dem Ergebnis für die Induktionsvorraussetzung wird das Ergebis für den Gesamtliste definiert. Für viele einfache Algorithmen ist dieser Weg recht einfach. Längenberechnung für Listen Ein einfacher Algorithmus für unsere induktive Listendefinition stellt die Berechnung der Länge der Liste dar, mit Hilfe der wir die einzelnen Elemente zählen. public int length () { 42 Listing 2.3: LiLi.java Zunächst als der Induktionsanfang. Das Ergebnis für die leere List. Dieses ist einfach. Eine leere Liste hat die Länge 0. 46 2.1 Einfach verkettete Listen i f ( isEmpty ( ) ) r e t u r n 0 ; 43 Listing 2.4: LiLi.java Anschließend können wir über die Induktionsvoraussetzung annehmen, dass die Funktion für die um ein Element kleinere Liste bereits definiert ist und das korrekte Ergebnis berechnet: f i n a l int tailResult = getTail () . length () ; 44 Listing 2.5: LiLi.java Schließlich ist zu überlegen, wie von dem Ergebnis der Voraussetzung das Gesamtergebnis zu definieren ist. Im Falle der Länge ist dieses besonders einfach, es muss 1 hinzugezählt werden: final int result = 1 + tailResult ; 45 Listing 2.6: LiLi.java Damit lässt sich das Ergebnis zurück geben: return r e s u l t ; 46 47 } Listing 2.7: LiLi.java Test auf ein Element Nach dem gleichen Schema lässt sich auch die Funktion schreiben, die testet, ob in der Liste ein bestimmtes Element enthalten ist: 48 p u b l i c b o o l e a n c o n t a i n s (A a ) { Listing 2.8: LiLi.java Zunächst als der Induktionsanfang. Das Ergebnis für die leere List. 49 i f ( isEmpty ( ) ) r e t u r n f a l s e ; Listing 2.9: LiLi.java Anschließend können wir über die wieder Induktionsvoraussetzung annehmen, dass die Funktion für die um ein Element kleinere Liste bereits definiert ist und das korrekte Ergebnis berechnet: 47 Kapitel 2 Hierarchische Strukturen f i n a l boolean t a i l R e s u l t = getTail ( ) . contains ( a ) ; 50 Listing 2.10: LiLi.java Schließlich ist zu überlegen, wie von dem Ergebnis der Voraussetzung das Gesamtergebnis zu definieren ist. In diesen Falle betrachten wir erst das erste Element und benutzen dann das Ergebnis der Induktionsvoraussetzung. f i n a l boolean r e s u l t = getHead ( ) . e q u a l s ( a ) | | t a i l R e s u l t ; 51 52 Listing 2.11: LiLi.java Damit haben wir das Ergebnis: return r e s u l t ; 53 } 54 Listing 2.12: LiLi.java Inhaltlich liegt eine komplett andere Aufgabenstellung als bei der Längenberechnung vor. Strukturell benutzen wir exakt dasselbe Vorgehen, das der vollständigen Induktion der Mathematik entspricht. Aneinanderhängen von Listen Nach dem gleichen Schema lässt sich auch die Funktion schreiben, die eine neue Liste erzeugt durch das Anhängen einer zweiten Liste.: p u b l i c LiLi append ( LiLi t h a t ) { 55 Listing 2.13: LiLi.java Zunächst als der Induktionsanfang. Das Ergebnis für die leere List. i f ( isEmpty ( ) ) r e t u r n t h a t ; 56 Listing 2.14: LiLi.java Anschließend können wir über die wieder Induktionsvoraussetzung annehmen, dass die Funktion für die um ein Element kleinere Liste bereits definiert ist und das korrekte Ergebnis berechnet: f i n a l LiLi t a i l R e s u l t = g e t T a i l ( ) . append ( t h a t ) ; 57 Listing 2.15: LiLi.java 48 2.1 Einfach verkettete Listen Schließlich ist zu überlegen, wie von dem Ergebnis der Voraussetzung das Gesamtergebnis zu definieren ist. In diesem Fall ist noch das erste Element an die Ergebnisliste vorne anzuhängen.: f i n a l LiLi r e s u l t 58 = new LiLi (getHead ( ) , t a i l R e s u l t ) ; Listing 2.16: LiLi.java Damit haben wir das Ergebnis: return r e s u l t ; 59 } 60 Listing 2.17: LiLi.java Inhaltlich liegt wieder eine komplett andere Aufgabenstellung als bei der Längenberechnung oder dem Test auf ein Element vor. Strukturell benutzen wir aber wieder exakt dasselbe Vorgehen, das der vollständigen Induktion der Mathematik entspricht. 61 } Listing 2.18: LiLi.java Aufgabe 1 Gegeben sei folgende Schnittstelle: 1 2 3 4 5 6 package name . p a n i t z . u t i l ; import j a v a . u t i l . f u n c t i o n . Consumer ; p u b l i c i n t e r f a c e OurList e x t e n d s I t e r a b l e { b o o l e a n isEmpty ( ) ; A head ( ) ; OurList t a i l ( ) ; 7 @Override i n t l e n g t h ( ) ; b o o l e a n c o n t a i n s (A e l ) ; v o i d machtAlle ( Consumer c ) ; OurList add (A e l ) ; 8 9 10 11 12 OurList drop ( i n t i ) ; OurList t a k e ( i n t i ) ; OurList s u b l i s t ( i n t s t a r t , i n t l e n g t h ) ; i n t indexOf (A e l ) ; A get ( i n t index ) ; 13 14 15 16 17 18 19 } Listing 2.19: OurList.java 49 Kapitel 2 Hierarchische Strukturen Sie sollen in dieser Aufgabe folgende noch nicht vollständige Klasse vervollständigen: 1 2 3 package name . p a n i t z . u t i l ; import j a v a . u t i l . f u n c t i o n . Consumer ; import j a v a . u t i l . I t e r a t o r ; 4 5 6 7 c l a s s L i s t e implements OurList{ p r i v a t e A head ; p r i v a t e OurList t a i l ; 8 p u b l i c L i s t e (A head , OurList t a i l ) { t h i s . head = head ; this . tail = tail ; } 9 10 11 12 13 public Liste () { this ( null , null ) ;} 14 15 p u b l i c L i s t e (A . . . xs ) { f o r (A x : xs ) { add ( x ) ; } } 16 17 18 19 20 21 @Override p u b l i c b o o l e a n isEmpty ( ) { r e t u r n head == n u l l && t a i l == n u l l ; } 22 23 24 25 @Override p u b l i c A head ( ) { r e t u r n head ; } @Override p u b l i c OurList t a i l ( ) { r e t u r n t a i l ; } 26 27 28 } Dabei sind die einzelnen zu implementierenden Methoden wie folgt spezifiziert. Schreiben Sie auch mehrere Testaufrufe. a) @Override int length(); Berechnet die Anzahl der Elemente in der Liste. b) boolean contains(A el); Wird wahr, wenn ein Element der Liste gleich dem übergebenen Argument ist. c) void machtAlle(Consumer c); Wendet die Methode des Consumer-Objekts auf alle Elemente an. 50 2.1 Einfach verkettete Listen d) OurList add(A el); Modifiziert die Liste, indem am Ende das neue Element hinzugefügt wird. e) OurList drop(int i); Gibt die Teilliste zurück, die entsteht, wenn die ersten i Elemente von der Liste genommen werden. Ist i zu groß, so wird eine leere Liste zurück gegeben. f) OurList take(int i); Gibt die Teilliste bestehend aus den ertsen i Elementen zurück. g) OurList sublist(int start, int length); Gibt eine Teilliste zurück, die von dem Index start beginnt und die spezifizierte Länge hat. Ist die Liste zu kurz, wird eine entsprechend kürzere bis hin zur leeren Liste zurück gegeben. h) int indexOf(A el); Gibt an, an welchem Index ein bestimmtes Element liegt. Ist das Element nicht enthalten, so wird -1 zurück gegeben. i) A get(int index); Gibt das Element an einem bestimmten Index zurück. Ist der Index zu groß, so wird null zurück gegeben. Aufgabe 2 (Nur für das Modul im Studiengang »Angewandte Informatik«) Gegeben sei folgende C-Header Datei: 1 #i n c l u d e 2 3 4 5 6 s t r u c t Li { v o i d * head ; s t r u c t Li * t a i l ; }; 7 8 t y p e d e f s t r u c t Li L i s t ; 9 10 11 12 b o o l isEmpty ( L i s t * t h i s ) ; L i s t * newEmptyList ( ) ; L i s t * newConsList ( v o i d * hd , L i s t * t l ) ; 13 14 void d e l e t e L i s t ( L i s t * t h i s ) ; 15 51 Kapitel 2 Hierarchische Strukturen 16 17 unsigned i n t length ( L i s t * t h i s ) ; L i s t * add ( L i s t * t h i s , v o i d * e l ) ; 18 19 20 21 22 23 24 List * List * List * List * List * void * copyList ( List * thi s ) ; append ( L i s t * t h i s , L i s t * t h a t ) ; drop ( L i s t * t h i s , i n t i ) ; take ( L i s t * this , i n t i ) ; s u b l i s t ( List * this , int start , int length ) ; get ( L i s t * this , i n t index ) ; 25 26 v o i d macheFuerAlle ( L i s t * t h i s , v o i d consumer ( v o i d * ) ) ; Listing 2.20: List.h Folgende Datei enthält eine rudimentäre Implementierung: 1 2 #i n c l u d e ” L i s t . h” #i n c l u d e < s t d l i b . h> 3 4 5 6 b o o l isEmpty ( L i s t * t h i s ) { r e t u r n t h i s −>head==NULL && t h i s −> t a i l==NULL; } 7 8 9 10 11 12 13 L i s t * newEmptyList ( ) { L i s t * t h i s = malloc ( s i z e o f ( L i s t ) ) ; t h i s −>head = NULL; t h i s −> t a i l = NULL; return this ; } 14 15 16 17 18 19 20 L i s t * newConsList ( v o i d * head , L i s t * t a i l ) { L i s t * t h i s = malloc ( s i z e o f ( L i s t ) ) ; t h i s −>head = head ; t h i s −> t a i l = t a i l ; return this ; } 21 22 23 24 25 void d e l e t e L i s t ( L i s t * t h i s ) { i f ( ! isEmpty ( t h i s ) ) d e l e t e L i s t ( t h i s −> t a i l ) ; free ( this ) ; } Listing 2.21: List.c Implementieren Sie die fehlenden Funktionen entsprechend der Spezifikation aus Aufgabe 1. 52 2.2 Bäume als verallgemeinerte Listen 2.2 Bäume als verallgemeinerte Listen In diesem Abschnitt soll jetzt das Prinzip der Listen verallgemeinert werden zu Bäumen, Listen sind nämlich nur ein Speziellfall einer Baumstruktur. Bäume sind ein gängiges Konzept um hierarchische Strukturen zu modellieren. Sie sind bekannt aus jeder Art von Baumdiagramme, wie Stammbäumen oder Firmenhierarchien. In der Informatik sind Bäume allgegenwärtig. Fast alle komplexen Daten stellen auf die eine oder andere Art einen Baum dar. Beispiele für Bäume sind mannigfach: • Dateisystem: Ein gängiges Dateisystem, wie es aus Unix, MacOS und Windows bekannt ist, stellt eine Baumstruktur dar. Es gibt einen ausgezeichneten Wurzelordner, von dem aus zu jeder Datei einen Pfad existiert. • Klassenhierarchie: Die Klassen in Java stellen mit ihrer Ableitungsrelation eine Baumstruktur dar. Die Klasse Object ist die Wurzel dieses Baumes, von der aus alle anderen Klassen über einen Pfad entlang der Ableitungsrelation erreicht werden können. • XML: Die logische Struktur eines XML-Dokuments ist ein Baum. Die Kinder eines Elements sind jeweils die Elemente, die durch das Element eigeschlossen sind. • Parserergebnisse: Ein Parser, der gemäß einer Grammatik prüft, ob ein bestimmter Satz zu einer Sprache gehört, erzeugt im Erfolgsfall eine Baumstruktur. Im nächsten Kapitel werden wir dieses im Detail kennenlernen. • Listen: Auch Listen sind Bäume, allerdings eine besondere Art, in denen jeder Knoten nur maximal ein Kind hat. • Berechnungsbäume: Zur statischen Analyse von Programmen, stellt man Bäume auf, in denen die Alternativen eines bedingten Ausdrucks Verzweigungen im Baum darstellen. • Tableaukalkül: Der Tableaukalkül ist ein Verfahren zum Beweis logischer Formeln. Die dabei verwendeten Tableaux sind Bäume. • Spielbäume: Alle möglichen Spielverläufe eines Spiels können als Baum dargestellt werden. Die Kanten entsprechen dabei einem Spielzug. • Prozesse: Auch die Prozesse eines Betriebssystems stellen eine Baumstruktur dar. Die Kinder eines Prozesses sind genau die Prozesse, die von ihm gestartet wurden. Wie man sieht, lohnt es sich, sich intensiv mit Bäumen vertraut zu machen, und man kann davon ausgehen, was immer in der Zukunft neues in der Informatik entwickelt werden wird, Bäume werden darin in irgendeiner Weise eine Rolle spielen. Ein Baum besteht aus einer Menge von Knoten die durch gerichtete Kanten verbunden sind. Die Kanten sind eine Relation auf den Knoten des Baumes. Die Kanten 53 Kapitel 2 Hierarchische Strukturen verbinden jeweils einen Elternknoten mit einem Kinderknoten. Ein Baum hat einen eindeutigen Wurzelknoten. Dieses ist der einzige Knoten, der keinen Elternknoten hat, d.h. es gibt keine Kante, die zu diesen Knoten führt. Knoten, die keinen Kinderknoten haben, d.h. von denen keine Kante ausgeht, heißen Blätter. Die Kinder eines Knotens sind geordnet, d.h. sie stehen in einer definierten Reihenfolge. Eine Folge von Kanten, in der der Endknoten einer Vorgängerkante der Ausgangsknoten der nächsten Kanten ist, heißt Pfad. In einem Baum darf es keine Zyklus geben, das heißt, es darf keinen Pfad geben, auf dem ein Knoten zweimal liegt. Knoten können in Bäumen markiert sein, z.B. einen Namen haben. Mitunter können auch Kanten eine Markierung tragen. Bäume, in denen Jeder Knoten maximal zwei Kinderknoten hat, nennt man Binärbäume. Bäume, in denen jeder Knoten maximal einen Kinderknoten hat, heißen Listen. 2.2.1 Formale Definition Auch Bäume werden wir induktiv definieren: Definition 2.2.1 Sei eine Menge E der Elemente gegeben. Die Menge der Bäume über E im Zeichen T reeE sei dann die kleinste Menge, für die gilt: • Der leere Baum ist ein Baum: Empty ∈ TreeE . • Sei e ∈ E und seien t1 , . . . , tn ∈ TreeE . Dann ist auch Branch(e, t1 , . . . , tn ) ∈ TreeE . Der einzige Unterschied zu unserer formalem Definition der Listen ist, dass im zweiten Punkt ein Baum aus n bereits existierenden Bäumen und einen neuem Element gebaut werden und nicht wie bei Listen aus einer existierenden Liste und einem Element. Wenn in der Definition der Bäume das n immer nur 1 ist, dann ist die Definition identisch mit der Definition von Listen. 2.2.2 Implementierung einer Baumklasse Die Definition eines Baumes war sehr ähnlich der Definition der Listen. Entsprechend ist auch die Implementierung sehr ähnlich zu unserer Listenimplementierung. Wir schreiben eine Klasse, die generisch über die Elemente, die an den Baumknoten stehen, ist: 54 2.2 Bäume als verallgemeinerte Listen 1 2 3 4 package name . p a n i t z . u t i l ; import j a v a . u t i l . L i s t ; import j a v a . u t i l . A r r a y L i s t ; import j a v a . u t i l . I t e r a t o r ; 5 6 7 p u b l i c c l a s s Tree implements I t e r a b l e { Listing 2.22: Tree.java Ebenso wie bei Listen, benötigen wir ein Feld, für das an dem Baumknoten stehende Element. Dieses wurde bei Listen meist als head bezeichnet. 8 p u b l i c E element = n u l l ; Listing 2.23: Tree.java Eine Listenzelle hatte einen Verweis auf die Restliste. Bei Bäumen gibt es nicht nur einen Unterbaum, sondern mehrere Unterbäume, die wiederum in einer Liste gespeichert werden. Hierzu nutzen wir die Standardliste. Also anstatt eines Feldes LiLi tail wie bei Listen, benötigen wir für Bäume ein Feld, das eine Liste von Teilbäumen enthält. 9 p u b l i c L i s t > c h i l d N o d e s = new A r r a y L i s t <>() ; Listing 2.24: Tree.java In einem boolschen Flag sei vermerkt, ob es sich um einen leeren Baum, der noch gar keinen Knoten enthält handelt oder nicht. 10 p u b l i c b o o l e a n isEmptyTree ; Listing 2.25: Tree.java Wir verketten unsere Bäume auch rückwärts, von den Kindern zu ihren Elternknoten. 11 p u b l i c Tree p a r e n t = n u l l ; Listing 2.26: Tree.java Laut Spezifikation gibt es zwei Konstruktoren. Den ersten zum Erzeugen eines leeren Baumes: 12 13 14 p u b l i c Tree ( ) { isEmptyTree = t r u e ; } Listing 2.27: Tree.java 55 Kapitel 2 Hierarchische Strukturen Der zweite erzeugt eine neue Baumwurzel mit einer bestimmten Markierung. p u b l i c Tree (E e , Tree . . . t s ) { element = e ; isEmptyTree = f a l s e ; f o r ( Tree t : t s ) { i f ( ! t . isEmptyTree ) { c h i l d N o d e s . add ( t ) ; t . parent = t h i s ; } } } 15 16 17 18 19 20 21 22 23 24 Listing 2.28: Tree.java Hier wird das Konstrukt einer variablen Parameteranzahl verwendet. Die drei Punkte an dem letzten Parameter zeigen an, dass hier 0 bis n Parameter eines Typs erwartet werden. Intern wird für diese Parameter ein Array erzeugt, über den wir in diesem Fall mit einer for-each Schleife iterieren. Für weitere kleine Tests seien ein paar Beispielbäume bereit gestellt. static static static static static static static static static static static static 25 26 27 28 29 30 31 32 33 34 35 36 Tree Tree Tree Tree Tree Tree Tree Tree Tree Tree Tree Tree a1 a2 a3 b1 a4 a5 b2 a6 a7 a8 b3 c1 = = = = = = = = = = = = new new new new new new new new new new new new Tree <>(” a1 ” ) ; Tree <>(” a2 ” ) ; Tree <>(” a3 ” ) ; Tree <>(” b1 ” , a1 , Tree <>(” a4 ” ) ; Tree <>(” a5 ” ) ; Tree <>(” b2 ” , a4 , Tree <>(” a6 ” ) ; Tree <>(” a7 ” ) ; Tree <>(” a8 ” ) ; Tree <>(” b3 ” , a6 , Tree <>(” c1 ” , b1 , a2 , a3 ) ; a5 ) ; a7 , a8 ) ; b2 , b3 ) ; Listing 2.29: Tree.java Als ein weniger abstraktes Beispiel seien die Nachfahren des englische Königs George VI als ein Stammbaum angegeben: s t a t i c Tree wind sor = new Tree<> ( ” George ” , new Tree<> ( ” Elizabeth ” , new Tree<> ( ” Charles ” , new Tree <>(” William ” , new Tree <>(” George ” ) ) , new Tree <>(” Henry ” ) 37 38 39 40 41 42 43 44 45 56 2.2 Bäume als verallgemeinerte Listen 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 ) , new Tree<> ( ”Andrew” , new Tree <>(” B e a t r i c e ” ) , new Tree <>(” Eugenie ” ) ) , new Tree<> ( ”Edward” , new Tree <>(” L o u i s e ” ) , new Tree <>(” James ” ) ) , new Tree<> ( ”Anne” , new Tree <>(” P e t e r ” ) , new Tree <>(” Zara ” ) ) ) , new Tree<> ( ” Magaret ” , new Tree<> ( ” David ” , new Tree <>(” C a r l e s ” ) , new Tree <>(” M a r a g r i t a ” ) ) 70 , new Tree <>(” Sarah ” ) ) 71 72 73 ); Listing 2.30: Tree.java Stringdarstellung Eine der ersten Methoden, die üblicher Weise für Klassen geschrieben wird, ist die Darstellung des Objektes als ein Text in Form eines Stringobjektes. Hierzu wird gängiger Weise die Methode toString aus der Klasse Objekt überschrieben. Für die Baumklassen wäre folgende Umsetzung denkbar, die wir allerdings auf Deutsch als alsString bezeichnet haben. 74 75 76 77 78 79 80 81 public String alsString () { i f ( isEmptyTree ) r e t u r n ”Empty ( ) ” ; S t r i n g r e s u l t = ” Branch ( ”+e l e m e n t+” ) [ ” ; f o r ( Tree c h i l d : c h i l d N o d e s ) { r e s u l t = r e s u l t+c h i l d . a l s S t r i n g ( ) ; } r e t u r n r e s u l t+” ] ” ; } Listing 2.31: Tree.java 57 Kapitel 2 Hierarchische Strukturen Diese Umsetzung ist rekursiv, indem die Methode für jeden Kindknoten rekursiv aufgerufen wird. Die Teilergebnisse für die Kindbäume werden dabei über die Stringkonkatenation mit dem eingebauten Operator + aneinander gehängt. Dieses ist nicht ganz unproblematisch. Stringobjekte sind in Java unveränderbar (unmutable). Ein einmal erzeugtes Stringobjekt wird nie verändert. Der plusOperator erzeugt aus zwei Stringobjekten ein neues Stringobjekt. Dieses geschieht dadurch, dass ein neues Objekt erzeugt wird und alle Zeichen der beiden Ausgangsobjekte in dieses neue Objekt kopiert werden. Wenn für die Berechnung eines Stringergebnisses viele Teilsstring mit dem plus-Operator aneinander gehängt werden, dann werden sehr viele Objekte erzeugt und insbesondere sehr viele Zeichen immer wieder im Speicher von einem Speicherbereich auf einen anderen kopiert.. Dieses werden wir im zweiten Teil der Vorlesung noch direkter sehen, wenn in der Programmiersprache C, die Operationen des plus-Operators explizit selbst programmiert werden müssen. Es empfiehlt sich also, bei der Konstruktion großer Texte, nicht gleich alle Teilstrings mit dem plus-Operator aneinander zu hängen und so permament Teilstrings zu kopieren. Strings puffern mit StringBuffer Das Java Standard-API stellt eine Klasse zur Verfügung, die es ermöglicht eine Folge von Strings zu sammeln, die dann im Endeffekt zu einem großen String konkateniert werden: die Klasse StringBuffer. Diese hat intern eine Liste der einzelnen aneinander zu hängenden Strings. Erst wenn alle Strings eingesammelt wurden, dann werden alle Strings zu einem großen String zusammen kopiert. Somit werden die Zeichen der einzelnen Strings nicht mehrfach im Speicher kopiert und das Anlegen temporärer Stringobjekte vermieden. Die Methode toString() kann mit Hilfe der Klasse StringBuffer effizienter umgesetzt werden. Es wird ein StringBuffer-Objekt angelegt. Mit der Methode append werden die einzelnen Strings dann diesem Objekt angefügt. Ein terminaler Aufruf der Methode toString auf dem StringBuffer-Objekt führt dann dazu, dass der Ergebnisstring erzeugt wird: Zunächst nehmen wir aber den einfachen Fall des leeren Baumes aus: @Override p u b l i c S t r i n g t o S t r i n g ( ) { i f ( isEmptyTree ) r e t u r n ”Empty ( ) ” ; 82 83 Listing 2.32: Tree.java Andernfalls wird ein StringBuffer-Objekt mit dem Anfang des Ergebnisstrings erzeugt: S t r i n g B u f f e r r e s u l t = new S t r i n g B u f f e r ( ” Branch ( ” ) ; 84 Listing 2.33: Tree.java 58 2.2 Bäume als verallgemeinerte Listen Diesem wird zunächst das Element in Stringdarstellung zugefügt: r e s u l t . append ( e l e m e n t . t o S t r i n g ( ) ) ; r e s u l t . append ( ” ) [ ” ) ; 85 86 Listing 2.34: Tree.java Schließlich werden noch die Kinder durchiteriert, um deren String-Darstellung dem StringBuffer-Objekt zuzufügen: f o r ( Tree c h i l d : c h i l d N o d e s ) { r e s u l t . append ( c h i l d . t o S t r i n g ( ) ) ; } r e s u l t . append ( ” ] ” ) ; 87 88 89 90 Listing 2.35: Tree.java Nun sind alle Teilstrings aufgesammelt und das Ergebnisobjekt kann erzeugt werden: return r e s u l t . toString () ; 91 92 } Listing 2.36: Tree.java String schreiben mit einem Writer Wer sich die letzte Lösung der Methode toString() etwas genauer angeschaut hat, der wird bemerkt haben, dass trotzdem viele temporäre Teilstrings erzeugt werden. Bei jedem rekursiven Aufruf, wird wieder ein neues StringBuffer-Objekt erzeugt, welches bei der Rückgabe einen neuen String erzeugt. Es werden also auch in dieser Umsetzung immer wieder dieselben Teilstrings im Speicher kopiert. Dieses Problem lässt sich nur umgehen, wenn man das StringBuffer-Objekt als Parameter an die Methode übergibt. Eine andere Möglichkeit, die noch flexibler ist, ermöglicht es Strings nicht explizit zu erzeugen, sondern in eine Datensenke zu schreiben. Hierzu dient die aus dem letzten Semester bekannte Klasse java.io.Writer. Von dieser gibt es unterschiedliche Implementierungen, die jeweils die Zeichen in unterschiedliche Datensenken schreiben. Häufig wird diese natürlich eine Datei sein, es kann aber auch über ein Netzwerk an einen Server geschrieben werden, oder aber auch wieder nur einfach in einen String. Um in ein Writer-Objekt zu schreiben, muss dieses wie am Ende des letzten Abschnitts erörtert, als Parameter übergeben werde. Zusätzlich ist zu beachten, dass die IO-Operationen zu Ausnahmen führen können: 93 p u b l i c v o i d w r i t e A s S t r i n g ( j a v a . i o . Writer out ) throws j a v a . i o . IOException { Listing 2.37: Tree.java 59 Kapitel 2 Hierarchische Strukturen Im Falle des leeren Baumes, wird dessen Darstellung geschrieben und die Methode verlassen: i f ( isEmptyTree ) { out . w r i t e ( ”Empty ( ) ” ) ; return ; } 94 95 96 97 Listing 2.38: Tree.java Wenn es sich um einen nichtleeren Baum handelt, können jetzt die einzelnen Teile nacheinander in den Writer geschrieben werden. Ähnlich wie in der vorherigen Lösung die einzelnen Teile dem StringBuffer angehängt wurden. out . w r i t e ( ” Branch ( ” ) ; out . w r i t e ( e l e m e n t . t o S t r i n g ( ) ) ; out . w r i t e ( ” ) [ ” ) ; 98 99 100 Listing 2.39: Tree.java Der Unterschied zu der vorherigen Lösung ist, dass jetzt die rekursiven Aufrufe den Writer als Parameter übergeben. f o r ( Tree c h i l d : c h i l d N o d e s ) { c h i l d . w r i t e A s S t r i n g ( out ) ; } out . w r i t e ( ” ] ” ) ; 101 102 103 104 } 105 Listing 2.40: Tree.java Um diese Implementierung jetzt auf gleiche Weise nutzen zu können wie die Standardmethode toString, kann ein StringWriter-Objekt erzeugt werden, in den der String geschrieben wird. public String asString () { try { j a v a . i o . S t r i n g W r i t e r out = new j a v a . i o . S t r i n g W r i t e r ( ) ; w r i t e A s S t r i n g ( out ) ; r e t u r n out . t o S t r i n g ( ) ; } c a t c h ( j a v a . i o . IOException e ) { return ”” ; } } 106 107 108 109 110 111 112 113 114 Listing 2.41: Tree.java Mit dieser Implementierung ist unnötiges Kopieren von Teilstrings komplett vermieden worden. zusätzlich ist sie flexibler, weil die Methode writeAsString nicht nur zum Erzeugen eines Strings genutzt werden kann, sondern auch dazu, die Stringdarstellung in unterschiedliche Datensenken zu schreiben. 60 2.2 Bäume als verallgemeinerte Listen Elemente in einer Liste gesammelt Ist man nicht mehr an der Baumstruktur interessiert, so kann man sich die Elemente eines Baums in einer Liste speichern. 115 116 117 118 119 p u b l i c j a v a . u t i l . L i s t a s L i s t ( ) { r e t u r n a s L i s t ( new j a v a . u t i l . L i n k e d L i s t <>() ) ; } p u b l i c L i s t a s L i s t ( L i s t r e s u l t ) { i f ( ! isEmptyTree ) r e s u l t . add ( e l e m e n t ) ; 120 f o r ( Tree c h i l d : c h i l d N o d e s ) c h i l d . a s L i s t ( r e s u l t ) ; 121 122 return r e s u l t ; 123 124 } Listing 2.42: Tree.java Ein einfacher Iterator über die Listendarstellung Mit der Listensammlung aller Elemente eines Baumes lässt sich schließlich leicht ein Baum über alle seine Elemente iterierbar machen. Es muss nur die Liste erzeugt werden und über diese iteriert werden. 125 126 127 128 @Override p u b l i c j a v a . u t i l . I t e r a t o r i t e r a t o r ( ) { return asList () . i t e r a t o r () ; } Listing 2.43: Tree.java Rekursive Methoden mit reduce auf Kindknoten Mit den Möglichkeiten von Java 8 lassen sich fast alle Algorithmen, die in den Baum absteigen, um dort Informationen zu sammeln, auch ohne explizite Schleifen sondern durch Methode höherer Ordnung auf die Kinderliste ausdrücken. Ob das immer besser ist, sei allerdings dahingestellt. Betrachten wir zunächst die Größe eines Baumes. Diese lässt sich als ein einziger Ausdruck formulieren. Zunächst wird durch den Bedingungsoperator der Spezialfall eines leeren Baumes behandelt. Ansonsten ist es 1 zur Summe der Größen aller Kinderbäume: 129 130 131 public int size () { r e t u r n isEmptyTree ? 0 : 1+c h i l d N o d e s 61 Kapitel 2 Hierarchische Strukturen . parallelStream () . r e d u c e ( 0 , ( r e s u l t , c h i l d ) −> r e s u l t+c h i l d . s i z e ( ) , Math : : addExact ) 132 133 ; } 134 Listing 2.44: Tree.java Die Summe der Kinderbäume ist mit der Methode reduce auf Stream gelöst. Diese entspricht der Methode reduce, wie wir sie selbst in dem eigenen Interface Iterable umgesetzt haben. Sie bekommt allerdings zwei Funktionsobjekte. Die erste Funktion beschreibt, wie ein einzelnen Element sein Ergebnis zum Gesamtergebnis hinzuzählt. In unserem Fall, wird von jedem Kind die Größe zum Gesamtergebnis addiert. Die zweite Funktion beschreibt, wie Teilergebnisse zu verknüpfen sind. In unserem Fall durch die Addition. Diese zwei Funktionen werden gebraucht, wenn der Stream, auf dem gearbeitet wird, parallelisiert wird. Dann wird z.B. vielleicht parallel das Teilergebnis der ersten Hälfte der Kinder und der zweiten Hälfte der Kinder berechnet. Diese Teilergebnisse sind zu addieren. Da wir unsere Bäume unser Interface Iterable haben implementieren lassen, können wir aber sehr einfach die dort als default definierte Methode reduce benutzen. Hier erhalten wir allerdings keine Parallelisierung und erzeugen zusätzlich über die Methode iterator() der Klasse Tree erst eine Liste aller Elemente. public int size2 () { r e t u r n r e d u c e ( 0 , ( r e s u l t , elem ) −> r e s u l t + 1 ) ; } 135 136 137 Listing 2.45: Tree.java Sehr sehr ähnlich sieht nun die Lösung für eine gänzlich andere Fragestellung aus. Was ist die maximale Tiefe des Baumes, also wie viele Generationen hat der Baum. Statt die Teilergebnisse aufzuaddieren, nehmen wir jetzt immer das Maximum der Teilergebnisse. Auch diese Aufgabe ist wunderbar parallelisierbar: public int generations () { r e t u r n isEmptyTree ? 0 : 1 + childNodes . parallelStream () . r e d u c e ( 0 , ( r e s u l t , c h i l d )−>Math . max( r e s u l t , c h i l d . g e n e r a t i o n s ( ) ) , Math : : max) ; } 138 139 140 141 142 143 144 145 } Listing 2.46: Tree.java Aufgabe 1 Schreiben Sie für die Klasse Tree eine Methode 62 2.2 Bäume als verallgemeinerte Listen List fringe(), die die Liste der Blätter des Baumes zurück gibt. Ein Blatt ist dabei ein Baumknoten, der keine Kinder mehr hat. Lösung Listing 2.47: Tree.java Aufgabe 1 Lösung in der Woche vom 19.5. in der Praktikumsstunde. Ergänzen Sie Ihre Klasse Tree aus dem letzten Übungsblatt um die folgende Methode: public void writeAsXML(java.io.Writer out) throws IOException; Es soll der Baum serialisiert als ein XML-Dokument in das Writer-Objekt geschrieben werden. Aufgabe 2 Schreiben Sie für die Klasse Tree eine Methode List pathTo(E elem), die die Elemente auf dem Pfad von der Wurzel zu dem Knoten mit der Element-Markierung elem zurück gibt. Sollte kein Knoten mit dem Element elem markiert sein, so ist die leer Liste das Ergebnis. Aufgabe 2 Lösung in der Woche vom 19.5. in der Praktikumsstunde. Schreiben Sie für die Klasse Tree eine Methode: public static Tree readFromXML(java.io.File xmlFile) throws Exception; Es soll ein neuen Baum erzeugt werden, der aus einer XML-Datei gelesen wird. Hierbei wird davon ausgegangen, dass die XML-Datei mit der Methode aus Aufgabe 1 geschrieben wurde. Benutzen Sie hierbei das DOM API. Aufgabe 3 Schreiben Sie für die Klasse Tree eine Methode boolean contains(java.util.function.Predicate pred), die genau dann wahr als Ergebnis hat, wenn für eines der Elemente im Baum das übergebene Prädikat wahr ist. 63 Kapitel 2 Hierarchische Strukturen Aufgabe 3 Nur für die Angewandte Informatik. Abgabe dieser Aufgabe bis Montag 26.5. per Mail an Leiter der Praktikumsstunde. Gegeben sei folgende Header Datei aus der Vorlesung: 1 2 #i f n d e f BIN_TREE__H #d e f i n e BIN_TREE__H 3 4 #i n c l u d e 5 6 typedef bool l e ( void * , void *) ; 7 8 9 10 11 12 13 14 s t r u c t TR{ /* im p r i n z i p i s t v o i d * s o etwas wie i r g e n d w a s a l s o : Object */ v o i d * head ; /* l i n k e s Kind ( d a r f NULL s e i n ) */ s t r u c t TR* l e f t ; /* l i n k e s Kind ( d a r f NULL s e i n ) */ s t r u c t TR* r i g h t ; 15 /* d i e O r d n u n g s r e l a t i o n . s o l l t e im j e d e n Knoten d e r s e l b e Z e i g e r s e i n */ le * kleinerGleich ; 16 17 18 }; 19 20 21 t y p e d e f s t r u c t TR Tree ; t y p e d e f u n s i g n e d i n t nat ; 22 23 b o o l isEmpty ( Tree * t h i s ) ; 24 25 26 /* mache e i n e l e e r e Menge */ Tree * newEmptyTree ( l e k l e i n e r G l e i c h ) ; 27 28 29 /* mache e i n e e i n e l e m e n t i g e Menge */ Tree * newLeafTree ( l e k l e i n e r G l e i c h , v o i d * elem ) ; 30 31 32 /* l ö s c h e a l l e Baumknoten , a b e r n i c h t d i e Elemente */ v o i d d e l e t e T r e e ( Tree * t h i s ) ; 33 34 /* u n s i g n e d i n t */ nat l e n g t h ( Tree * t h i s ) ; 35 36 37 /* Algorithmus f ü r das Hinzufügen e i n e s Elements : 38 39 40 41 42 43 44 45 64 1 . Schaue ob t h i s l e e r e r Baum : dann t h i s −>head = e l 2. sonst v e r g l e i c h e : t h i s −>k l e i n e r G l e i c h ( e l , head ) a ) Ja ! dann c h e c k e : t h i s −>k l e i n e r G l e i c h ( head , e l ) i ) Ja ! dann r e t u r n ( ohne w e i t e r e a k t i o n ) i i ) s o n s t add ( t h i s −>l e f t , e l ) ( wenn t h i s −> l e f t n i c h t NULL) ( o d e r t h i s −> l e f t = newLeafTree ( t h i s −> 2.2 Bäume als verallgemeinerte Listen kleinerGleich , el ) b ) a n a l o g wie oben nur r e c h t e S e i t e r e c h t s 46 47 48 */ v o i d add ( Tree * t h i s , v o i d * e l ) ; 49 50 51 /* k o p i e r e den Baum, a b e r n i c h t d i e Elemente */ Tree * copyTree ( Tree * t h i s ) ; 52 53 54 55 /* e n t h ä l t d e r Baum e i n bestimmtes Element . H i e r z u wird d i e O r d n u n g s r e l a t i o n d e s Baumes genommen . x==y b e d e u t e t : k l e i n e r G l e i c h ( x , y ) && k l e i n e r G l e i c h ( y , x ) . */ 56 57 b o o l c o n t a i n s ( Tree * t h i s , v o i d * elem ) ; 58 59 60 /* mache etwas f ü r a l l e Elemente , d i e vom Baum r e f e r e n z i e r t werden . */ v o i d macheFuerAlle ( Tree * t h i s , v o i d consumer ( v o i d * ) ) ; 61 62 /* B e i s p i e l f ü r i n t Z e i g e r : 63 64 65 66 b o o l k l G l ( v o i d * xp , v o i d * yp ) { r e t u r n * ( ( i n t * ) xp ) <= * ( ( i n t * ) yp ) ; } 67 68 69 70 void p r i n t I n t ( void * p) { p r i n t f (”%d ” , * ( ( i n t * ) p ) ) ; } 71 72 73 74 75 76 i n t main ( ) { i n t xs [ ] = { 3 4 , 5 , 2 3 , 6 , 5 3 4 5 , − 3 4 , 4 3 , − 4 3 1 , 1 , 4 3 , 5 4 4 5 , − 2 6 5 } ; Tree * t = newEmptyTree ( k l G l ) ; int i ; f o r ( i =0; i < 1 2 ; i ++) add ( t , xs+i ) ; 77 macheFuerAlle ( t , p r i n t I n t ) ; 78 79 deleteTree ( t ) ; 80 81 return 0; 82 83 } 84 85 86 */ #e n d i f Listing 2.48: BinTree.h Schreiben Sie die Implementierung dieser header Datei, so dass sie einen Binärbaum erhalten, der eine Menge realisiert. 1 2 #i n c l u d e ” BinTree . h” #i n c l u d e < s t d l i b . h> 65 Kapitel 2 Hierarchische Strukturen 3 #i n c l u d e 4 5 6 7 8 9 b o o l isEmpty ( Tree * t h i s ) { r e t u r n t h i s −>head==NULL && t h i s −> l e f t == NULL && t h i s −>r i g h t == NULL; } 10 11 12 13 Tree * newEmptyTree ( l e k l e i n e r G l e i c h ) { r e t u r n newLeafTree ( k l e i n e r G l e i c h ,NULL) ; } 14 15 16 17 18 19 20 21 22 23 /* mache e i n e e i n e l e m e n t i g e Menge */ Tree * newLeafTree ( l e k l e i n e r G l e i c h , v o i d * elem ) { Tree * t h i s = m a l l o c ( s i z e o f ( Tree ) ) ; t h i s −> l e f t = NULL; t h i s −> r i g h t = NULL; t h i s −> k l e i n e r G l e i c h = k l e i n e r G l e i c h ; t h i s −> head = elem ; return this ; } 24 25 26 27 28 29 30 /* l ö s c h e a l l e Baumknoten , a b e r n i c h t d i e Elemente */ v o i d d e l e t e T r e e ( Tree * t h i s ) { i f (NULL != t h i s −> l e f t ) d e l e t e T r e e ( t h i s −> l e f t ) ; i f (NULL != t h i s −>r i g h t ) d e l e t e T r e e ( t h i s −>r i g h t ) ; free ( this ) ; } 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 v o i d add ( Tree * t h i s , v o i d * e l ) { i f ( isEmpty ( t h i s ) ) { t h i s −> head = e l ; } else { i f ( t h i s −>k l e i n e r G l e i c h ( e l , t h i s −>head ) ) { i f ( t h i s −>k l e i n e r G l e i c h ( t h i s −>head , e l ) ) r e t u r n ; i f (NULL == t h i s −> l e f t ) { t h i s −> l e f t = newLeafTree ( t h i s −>k l e i n e r G l e i c h , e l ) ; }else { add ( t h i s −>l e f t , e l ) ; } } else { i f (NULL == t h i s −> r i g h t ) { t h i s −> r i g h t = newLeafTree ( t h i s −>k l e i n e r G l e i c h , e l ) ; }else { add ( t h i s −>r i g h t , e l ) ; } } } } 52 53 54 66 v o i d macheFuerAlle ( Tree * t h i s , v o i d consumer ( v o i d * ) ) { i f ( t h i s == NULL) r e t u r n ; 2.3 XML als serialisierte Baumstruktur i f ( isEmpty ( t h i s ) ) r e t u r n ; macheFuerAlle ( t h i s −> l e f t , consumer ) ; consumer ( t h i s −>head ) ; macheFuerAlle ( t h i s −> r i g h t , consumer ) ; 55 56 57 58 59 } 60 61 62 63 void p r i n t I n t ( void * p) { p r i n t f ( ”%d ” , * ( ( i n t * ) p ) ) ; } 64 65 66 67 68 69 70 71 72 73 b o o l c o n t a i n s ( Tree * t h i s , v o i d * e l ) { i f ( t h i s == NULL) r e t u r n f a l s e ; i f ( isEmpty ( t h i s ) ) r e t u r n f a l s e ; i f ( t h i s −>k l e i n e r G l e i c h ( e l , t h i s −> head )&&t h i s −>k l e i n e r G l e i c h ( t h i s −> head , e l ) ) return true ; i f ( t h i s −>k l e i n e r G l e i c h ( e l , t h i s −> head ) ) { r e t u r n c o n t a i n s ( t h i s −> l e f t , e l ) ; } r e t u r n c o n t a i n s ( t h i s −> r i g h t , e l ) ; 74 75 } 76 77 78 79 b o o l k l G l ( v o i d * xp , v o i d * yp ) { r e t u r n * ( ( i n t * ) xp ) <= * ( ( i n t * ) yp ) ; } Listing 2.49: BinTree.c Aufgabe 4 Schreiben Sie für die Klasse Tree eine Methode boolean contains(E el), die genau dann wahr als Ergebnis hat, wenn eines der Elemente im Baum gleich dem übergebenen Element ist. Aufgabe 5 Schreiben Sie für die Klasse Tree eine Methode Tree mapTree(java.util.function.Function f), die einen neuen Baum erzeugt, dessen Elemente aus den Elementen des Ursprungsbaums mit Hilfe der übergebenen Funktion erzeugt werden. 2.3 XML als serialisierte Baumstruktur XML ist eine Sprache, die es erlaubt Dokumente mit einer logischen Struktur zu beschreiben. Die Grundidee dahinter ist, die logische Struktur eines Dokuments 67 Kapitel 2 Hierarchische Strukturen von seiner Visualisierung zu trennen. Ein Dokument mit einer bestimmten logischen Struktur kann für verschiedene Medien unterschiedlich visualisiert werden, z.B. als HTML-Dokument für die Darstellung in einem Webbrowser, als pdf- oder postscript-Datei für den Druck des Dokuments und das für unterschiedliche Druckformate. Eventuell sollen nicht alle Teile eines Dokuments visualisiert werden. XML ist zunächst eine Sprache, die logisch strukturierte Dokumente zu schreiben, erlaubt. Dokumente bestehen hierbei aus den eigentlichen Dokumenttext und zusätzlich aus Markierungen dieses Textes. Die Markierungen sind in spitzen Klammern eingeschlossen. Beispiel 2.3.1 Der eigentliche Text des Dokuments sei: 1 The B e a t l e s White Album Die einzelnen Bestandteile dieses Textes können markiert werden: 1 2 3 4 < a r t i s t >The B e a t l e s < t i t l e >White Album Die XML-Sprache wird durch ein Industriekonsortium definiert, dem W3C. Dieses ist ein Zusammenschluß vieler Firmen, die ein gemeinsames Interesse eines allgemeinen Standards für eine Markierungssprache haben. Die eigentlichen Standards des W3C heißen nicht Standard, sondern Empfehlung (recommendation), weil es sich bei dem W3C nicht um eine staatliche oder überstaatliche Standartisiertungsbehörde handelt. Die aktuelle Empfehlung für XML liegt seit August 2006 als Empfehlung in der Version 1.1 vor . XML enstand Ende der 90er Jahre und ist abgeleitet von einer umfangreicheren Dokumentenbeschreibungssprache: SGML. Der SGML-Standard ist wesentlich komplizierter und krankt daran, daß es extrem schwer ist, Software für die Verarbeitung von SGML-Dokumenten zu entwickeln. Daher fasste SGML nur Fuß in Bereichen, wo gut strukturierte, leicht wartbare Dokumente von fundamentaler Bedeutung waren, so dass die Investition in teure Werkzeuge zur Erzeugung und Pflege von SGML-Dokumenten sich rentierte. Dies waren z.B. Dokumentationen im Luftfahrtbereich.3 Die Idee bei der Entwicklung von XML war: eine Sprache mit den Vorteilen von SGML zu Entwickeln, die klein, übersichtlich und leicht zu handhaben ist. 3 Man sagt, ein Pilot brauche den Copiloten, damit dieser die Handbücher für das Flugzeug trägt. 68 2.4 Das syntaktische XML-Format Wie wir im Laufe des Kapitels sehen werden, ist die logische Struktur eines XMLDokuments auch wieder eine hierarchische Baumstruktur, so dass wir alle unsere Überlegungen zu Bäumen direkt auf XML-Dokumente anwenden können. 2.4 Das syntaktische XML-Format Die grundlegendste Empfehlung des W3C legt fest, wann ein Dokument ein gültiges XML-Dokument ist, die Syntax eines XML-Dokuments. Die nächsten Abschnitte stellen die wichtigsten Bestandteile eines XML-Dokuments vor. Jedes Dokument beginnt mit einer Anfangszeile, in dem das Dokument angibt, daß es ein XML-Dokument nach einer bestimmten Version der XML Empfehlung ist: 1 Dieses ist die erste Zeile eines XML-Dokuments. Vor dieser Zeile darf kein Leerzeichen stehen. Die derzeitig aktuellste und einzige Version der XML-Empfehlung ist die Version 1.1. vom 16. August 2006. Nach Aussage eines Mitglieds des W3C ist es sehr unwahrscheinlich, dass es jemals eine Version 2.0 von XML geben wird. Zuviele weitere Techniken und Empfehlungen basieren auf XML, so daß die Definition von dem, was ein XML-Dokument ist kaum mehr in größeren Rahmen zu ändern ist. 2.4.1 Elementknoten Der Hauptbestandteil eines XML-Dokuments sind die Elemente. Dieses sind mit der Spitzenklammernotation um Teile des Dokuments gemachte Markierungen. Ein Element hat einen Tagnamen, der ein beliebiges Wort ohne Leerzeichen sein kann. Für einen Tagnamen name beginnt ein Element mit und endet mit . Zwischen dieser Start- und Endemarkierung eines Elements kann Text oder auch weitere Elemente stehen. Es wird für XML-Dokument verlangt, daß es genau ein einziges oberstes Element hat. Beispiel 2.4.1 Somit ist ein einfaches XML-Dokument ein solches Dokument, in dem der gesammte Text mit einem einzigen Element markiert ist: 1 2 3 4 D i e s e s i s t d e r Text d e s Dokuments . Er i s t mit genau einem Element m a r k i e r t . 69 Kapitel 2 Hierarchische Strukturen Im einführenden Beispiel haben wir schon ein XML-Dokument gesehen, das mehrere Elemente hat. Dort umschließt das Element zwei weitere Elemente, die Elemente und . Die Teile, die ein Element umschließt, werden der Inhalt des Elements genannt. Ein Element kann auch keinen, sprich den leeren Inhalt haben. Dann folgt der öffnenden Markierung direkt die schließende Markierung. Beispiel 2.4.2 Folgendes Dokument enthält ein Element ohne Inhalt: 1 2 3 4 5 6 <?xml v e r s i o n=” 1 . 0 ”?> <s k r i p t > <page>e r s t e S e i t e </page> <page></page> <page>d r i t t e S e i t e </page> </ s k r i p t > 2.4.2 Leere Elemente Für ein Element mit Tagnamen name, das keinen Inhalt hat, gibt es die abkürzenden Schreibweise: <name/> Beispiel 2.4.3 Das vorherige Dokument läßt sich somit auch wie folgt schreiben: 1 2 3 4 5 6 <?xml v e r s i o n=” 1 . 0 ”?> <s k r i p t > <page>e r s t e S e i t e </page> <page/> <page>d r i t t e S e i t e </page> </ s k r i p t > 2.4.3 Gemischter Inhalt Die bisherigen Beispiele haben nur Elemente gehabt, deren Inhalt entweder Elemente oder Text waren, aber nicht beides. Es ist aber auch möglich Elemente mit Text und Elementen als Inhalt zu schreiben. Man spricht dann vom gemischten Inhalt (mixed content). Beispiel 2.4.4 Ein Dokument, in dem das oberste Element einen gemischten Inhalt hat: 70 2.4 Das syntaktische XML-Format 1 2 3 4 5 6 <?xml v e r s i o n=” 1 . 0 ”?> <myText>Der <landsmann>I t a l i e n e r </landsmann> <eigename>Ferdinand C a r u l l i </eigename> war a l s G i t a r r i s t e b e n s o wie d e r <landsmann>Spanier </landsmann> <eigename>Fernando Sor</eigename> i n <o r t >Paris </o r t > a n s ä ß i g .</myText> Tatsächlich ist gemischter Inhalt nicht bei jedermann beliebt. Es gibt zwei große Anwendungshintergründe für XML. Zum einen die Dokumentenbeschreibung. In diesem Kontext ist gemischter ganz natürlich, wie man am obigen Beispiel erkennen kann. Die zweite Fraktion von Anwendern, die XML nutzt, kommt eher aus dem Bereich allgemeiner Datenverarbeitung. Hier stehen eher Daten, wie in Datenbanken im Vordergrund und nicht Textdokumente. Es wird da zum Beispiel XML genutzt um an einen Webservice Daten zu übermitteln, die dort verarbeitet werden sollen. In diesem Kontext stört gemischter Inhalt mitunter oder hat zumindest wenig Sinn. Die Problematik wird sofort ersichtlich, wenn man sich überlegt, dass Leerzeichen auch Text sind. Unter dieser Prämisse haben mit Sicherheit alle XML-Dokumente gemischten Inhalt, weil zur optischen besseren Lesbarkeit auch in einem Datendokument, das eigentlich keinen gemischten Inhalt hat, zwischen den einzelnen Elementknoten Leerzeichen in Form von Einrückungen und neuen Zeilen stehen. 2.4.4 XML Dokumente sind Bäume Die wohl wichtigste Beschränkung für XML-Dokumente ist, dass sie eine hierarchische Struktur darstellen müssen. Zwei Elemente dürfen sich nicht überlappen. Ein Element darf erst wieder geschlossen werden, wenn alle nach ihm geöffneten Elemente wieder geschlossen wurden. Beispiel 2.4.5 Das folgende ist kein gültiges XML-Dokument. Das Element <bf> wird geschlossen bevor das später geöffnete Element <em> geschlossen worde. 1 2 3 4 5 <?xml v e r s i o n=” 1 . 0 ”?> <i l l e g a l D o c u m e n t > <b f >f e t t e S c h r i f t <em>k u r s i v und f e t t </b f > nur noch k u r s i v </em>. </i l l e g a l D o c u m e n t > Das Dokument wäre wie folgt als gültiges XML zu schreiben: 71 Kapitel 2 Hierarchische Strukturen 1 2 3 4 5 <?xml v e r s i o n=” 1 . 0 ”?> <validDocument> <b f >f e t t e S c h r i f t <em>k u r s i v und f e t t </em></b f > <em>nur noch k u r s i v </em>. </validDocument> Dieses Dokument hat eine hierarchische Struktur. Die hierarchische Struktur eines XML-Dokuments lässt sich in der Regel gut erkennen, wenn man das Dokument in einem Web-Browser öffnet. Fast alle Web-Browser stellen dann die logische Baumstruktur so dar, dass die Kindknoten eines Elementknoten aufgeklappt und zugeklappt werden können. 2.4.5 Chracter Entities Sobald in einem XML-Dokument eine der spitzen Klammern < oder > auftaucht, wird dieses als Teil eines Elementtags interpretiert. Sollen diese Zeichen hingegen als Text und nicht als Teil der Markierung benutzt werden, sind also Bestandteil des Dokumenttextes, so muss man einen Fluchtmechanismus für diese Zeichen benutzen. Diese Fluchtmechanismen nennt man character entities. Eine Character Entity beginnt in XML mit dem Zeichen & und endet mit einem Semikolon ;. Dazwischen steht der Name des Buchstabens. XML kennt die folgenden Character Entities: Entity Zeichen Beschreibung < < (less than) > > (greater than) & & (ampersant) " " (quotation mark) ' ' (apostroph) Somit lassen sich in XML auch Dokumente schreiben, die diese Zeichen als Text beinhalten. Beispiel 2.4.6 Folgendes Dokument benutzt Character Entities um mathematische Formeln zu schreiben: 1 2 3 4 5 <?xml v e r s i o n=” 1 . 0 ”?> <g l e i c h u n g e n > <g l e i c h u n g >x+1&g t ; x</g l e i c h u n g > <g l e i c h u n g >x * x& l t ; x * x * x f ü r x&g t ;1</ g l e i c h u n g > </g l e i c h u n g e n > 72 2.4 Das syntaktische XML-Format 2.4.6 CData Sections Manchmal gibt es große Textabschnitte in denen Zeichen vorkommen, die eigentlich durch character entities zu umschreiben wären, weil sie in XML eine reservierte Bedeutung haben. XML bietet die Möglichkeit solche kompletten Abschnitte als eine sogenannte CData Section zu schreiben. Eine CData section beginnt mit der Zeichenfolge <![CDATA[ und endet mit der Zeichenfolge: ]]>. Dazwischen können beliebige Zeichenstehen, die eins zu eins als Text des Dokumentes interpretiert werden. Beispiel 2.4.7 Die im vorherigen Beispiel mit Character Entities beschriebenen Formeln lassen sich innerhalb einer CDATA-Section wie folgt schreiben. 1 2 3 4 5 <?xml v e r s i o n=” 1 . 0 ”?> <formeln ><![CDATA[ x+1>x x *x<x * x * x f ü r x > 1 ]]></ formeln > Strukturell sind die CDATA-Sektionen auch nur Textknoten im Dokument. Sie unterscheiden sich von anderen Textknoten nur in der Darstellung in der serialisierten Textform. 2.4.7 Kommentare XML stellt auch eine Möglichkeit zur Verfügung, bestimmte Texte als Kommentar einem Dokument zuzufügen. Diese Kommentare werden mit <!-- begonnen und mit --> beendet. Kommentartexte sind nicht Bestandteil des eigentlichen Dokumenttextes. Beispiel 2.4.8 Im folgenden Dokument ist ein Kommentar eingefügt: 1 2 3 4 5 6 7 8 9 <?xml v e r s i o n=” 1 . 0 ”?> <drehbuch> < t i t e l >Ben Hur</ t i t e l > <akt > <s z e ne >Ben Hur am Vorabend d e s Wagenrennens . <!−−D i e s e Szene muß noch a u s g e a r b e i t e t werden.−−> </s ze ne > </a kt > </drehbuch> Kommentarknoten werden nicht als eigentliche Knoten der Baumstruktur des Dokuments betrachtet. 73 Kapitel 2 Hierarchische Strukturen 2.4.8 Processing Instructions In einem XML-Dokument können Anweisung stehen, die angeben, was mit einem Dokument von einem externen Programm zu tun ist. Solche Anweisungen können z.B. angeben, mit welchen Mitteln das Dokument visualisiert werden soll. Wir werden hierzu im nächsten Kapitel ein Beispiel sehen. Syntaktisch beginnt eine processing instruction mit <? und endet mit ?>. Dazwischen stehen wie in der Attributschreibweise Werte für den Typ der Anweisung und eine Referenz auf eine externe Quelle. Beispiel 2.4.9 Ausschnitt aus dem XML-Dokument diesen Skripts, in dem auf ein Stylesheet verwiesen wird, daß das Skript in eine HTML-Darstellung umwandelt: 1 2 3 4 <?xml v e r s i o n=” 1 . 0 ”?> <?xml−s t y l e s h e e t t y p e=” t e x t / x s l ” h r e f=” . . / t r a n s f o r m s k r i p t . x s l ”?> 5 6 7 8 9 10 11 <s k r i p t > <t i t e l s e i t e > < t i t e l >Grundlagen d e r D a t e n v e r a r b e i t u n g <w h i t e/>I I </ t i t e l > <s e m e s t e r >WS 02/03</ s e m e s t e r > </ t i t e l s e i t e > </ s k r i p t > Processing Instruction-knoten werden nicht als eigentliche Knoten der Baumstruktur des Dokuments betrachtet. 2.4.9 Attribute Die Elemente eines XML-Dokuments können als zusätzliche Information auch noch Attribute haben. Attribute haben einen Namen und einen Wert. Syntaktisch ist ein Attribut dargestellt durch den Attributnamen gefolgt von einem Gleichheitszeichen gefolgt von dem in Anführungszeichen eingeschlossenen Attributwert. Attribute stehen im Starttag eines Elements. Attribute werden nicht als Bestandteils des eigentlichen Textes eines Dokuments betrachtet. Beispiel 2.4.10 Dokument mit einem Attribut für ein Element. 1 2 3 <?xml v e r s i o n=” 1 . 0 ”?> <t e x t >Mehr I n f o r m a t i o n zu XML f i n d e t man a u f den S e i t e n d e s < l i n k a d d r e s s=”www. w3c . o r g ”>W3C</ l i n k >.</ t e x t > 74 2.4 Das syntaktische XML-Format Mit Hilfe der Attribute ist es so möglich eine Abbildung an jeden Elementknoten zu definieren. Wie wir im letzten Semester gelernt haben, ist eine Abbildung eine Liste von Paaren. Die Paare sind dabei als Schlüssel/Wert-paare zu verstehen. Ein XMLAttribut stellt ein solches Schlüssel/Wert-Paar dar. Vorr dem Gleichheitszeichen steht der Schlüssel und eingeschlossen in Anführungsstrichen findet sich der Wert, auf dem der Schlüssel abgebildet wird. 2.4.10 Zeichencodierungen Im Zusammenhang mit IO-Operationen wurde im letzten Semester bereits eingeführt, dass Textdokumente in unterschiedlichen Encodings physikalisch auf einem Datenträger gespeichert werden können. Auch XML ist ein Dokumentenformat, das nicht auf eine Kultur mit einer bestimmten Schrift beschränkt ist, sondern in der Lage ist, alle im Unicode erfassten Zeichen darzustellen, seien es Zeichen der lateinischen, kyrillischen, arabischen, chinesischen oder sonst einer Schrift bis hin zur keltischen Keilschrift. Jedes Zeichen eines XML-Dokuments kann potentiell eines dieser mehreren zigtausend Zeichen einer der vielen Schriften sein. In der Regel benutzt ein XML-Dokument insbesondere im amerikanischen und europäischen Bereich nur wenige kaum 100 unterschiedliche Zeichen. Auch ein arabisches Dokument wird mit weniger als 100 verschiedenen Zeichen auskommen. Wenn ein Dokument im Computer auf der Festplatte gespeichert wird, so werden auf der Festplatte keine Zeichen einer Schrift, sondern Zahlen abgespeichert. Diese Zahlen sind traditionell Zahlen die 8 Bit im Speicher belegen, ein sogenannter Byte (auch Oktett). Ein Byte ist in der Lage 256 unterschiedliche Zahlen darzustellen. Damit würde ein Byte ausreichen, alle Buchstaben eines normalen westlichen Dokuments in lateinischer Schrift (oder eines arabischen Dokuments darzustellen). Für ein Chinesisches Dokument reicht es nicht aus, die Zeichen durch ein Byte allein auszudrücken, denn es gibt mehr als 10000 verschiedene chinesische Zeichen. Es ist notwendig, zwei Byte im Speicher zu benutzen, um die vielen chinesischen Zeichen als Zahlen darzustellen. Die Codierung eines Dokuments gibt nun an, wie die Zahlen, die der Computer auf der Festplatte gespeichert hat, als Zeichen interpretiert werden sollen. Eine Codierung für arabische Texte wird den Zahlen von 0 bis 255 bestimmte arabische Buchstaben zuordnen, eine Codierung für deutsche Dokumente wird den Zahlen 0 bis 255 lateinische Buchstaben inklusive deutscher Umlaute und dem ß zuordnen. Für ein chinesisches Dokument wird eine Codierung benötigt, die den 65536 mit 2 Byte darstellbaren Zahlen jeweils chinesische Zeichen zuordnet. Man sieht, dass es Codierungen geben muss, die für ein Zeichen ein Byte im Speicher belegen, und solche, die zwei Byte im Speicher belegen. Es gibt darüber hinaus auch eine Reihe Mischformen, manche Zeichen werden durch ein Byte andere durch 2 oder sogar durch 3 Byte dargestellt. Im Kopf eines XML-Dokuments kann angegeben werden, 75 Kapitel 2 Hierarchische Strukturen in welcher Codierung das Dokument abgespeichert ist. Beispiel 2.4.11 Dieses Skript ist in einer Codierung gespeichert, die für westeuropäische Dokumente gut geeignet ist, da es für die verschiedenen Sonderzeichen der westeuropäischen Schriften einen Zahlenwert im 8-Bit-Bereich zugeordnet hat. Die Codierung mit dem Namen: iso-8859-1. Diese wird im Kopf des Dokuments angegeben: 1 2 <?xml v e r s i o n=” 1 . 0 ” e n c o d i n g=” i s o −8859−1” ?> <s k r i p t ><k a p i t e l >b l a b l a b l a </ k a p i t e l ></s k r i p t > Wird keine Codierung im Kopf eines Dokuments angegeben, so wird als Standardcodierung die sogenannte utf-8 Codierung benutzt. In ihr belegen lateinische Zeichen einen Byte und Zeichen anderer Schriften (oder auch das Euro Symbol) zwei bis drei Bytes. Eine Codierung, in der alle Zeichen mindestens mit zwei Bytes dargestellt werden ist: utf-16, die Standardabbildung von Zeichen, wie sie im Unicode definiert ist. 2.4.11 DTD In der XML-Empfehlung integriert ist die Definition der document type description language, kurz DTD. Die DTD ermöglicht es zu formulieren, welche Tags in einem Dokument vorkommen sollen. DTD ist keine eigens für XML erfundene Sprache, sondern aus SGML geerbt. DTD-Dokumente sind keine XML-Dokumente, sondern haben eine eigene Syntax. Allerdings kann ein XML Dokument mit einer DTD beginnen. DTD-Abschnitte zu Beginn des Dokuments sind ein legaler und normaler Bestandteil eines XML Dokuments. Wir stellen im einzelnen diese Syntax vor: • <!DOCTYPE root-element [ doctype-declaration... ]> Legt den Namen des top-level Elements fest und enthält die gesammte Definition des erlaubten Inhalts. • <!ELEMENT element-name content-model> assoziiert einen content model mit allen Elementen, die diesen Namen haben. content models können wie folgt gebildet werden.: – EMPTY: Das Element hat keinen Inhalt. 76 2.4 Das syntaktische XML-Format – ANY: Das Element hat einen beliebigen Inhalt – #PCDATA: Zeichenkette, also der eigentliche Text. – durch einen regulären Ausdruck, der aus den folgenden Komponenten gebildet werden kann. * Auswahl von Alternativen (oder): (...|...|...) * Sequenz von Ausdrücken: (...,...,...) * Option: ...? * Ein bis mehrfach Wiederholung: ...* * Null bis mehrfache Wiederholung: ...+ • <!ATTLIST element-name attr-name attr-type attr-default ...> Liste, die definiert, was für Attribute ein Element hat: Attribute werden definiert durch: – CDATA: beliebiger Text ist erlaubt – (value|...): Aufzählung erlaubter Werte Attribut default sind: – #REQUIRED: Das Attribut muß immer vorhanden sein. – #IMPLIED: Das Attribut ist optional – "value": Standardwert, wenn das Attribut fehlt. – #FIXED "value": Attribut kennt nur diesen Wert. Beispiel 2.4.12 Ein Beispiel für eine DTD, die ein Format für eine Rezeptsammlung definiert. 1 2 <!DOCTYPE c o l l e c t i o n SYSTEM ” c o l l e c t i o n . d t d ” [ <!ELEMENT c o l l e c t i o n ( d e s c r i p t i o n , r e c i p e *)> 3 4 <!ELEMENT d e s c r i p t i o n ANY> 5 6 7 <!ELEMENT r e c i p e ( t i t l e , i n g r e d i e n t * , p r e p a r a t i o n , comment ? , n u t r i t i o n )> 8 9 <!ELEMENT t i t l e (#PCDATA)> 10 11 12 13 14 <!ELEMENT i n g r e d i e n t ( i n g r e d i e n t * , p r e p a r a t i o n )?> <!ATTLIST i n g r e d i e n t name CDATA #REQUIRED amount CDATA #IMPLIED u n i t CDATA #IMPLIED> 15 16 <!ELEMENT p r e p a r a t i o n ( s t e p )*> 77 Kapitel 2 Hierarchische Strukturen 17 18 <!ELEMENT s t e p (#PCDATA)> 19 20 <!ELEMENT comment (#PCDATA)> 21 22 23 24 25 <!ELEMENT n u t r i t i o n EMPTY> <!ATTLIST n u t r i t i o n f a t CDATA #REQUIRED c a l o r i e s CDATA #REQUIRED a l c o h o l CDATA #IMPLIED>]> Es ist auch möglich DTDs als externe Dateien zu haben und zu Beginn eines XMLDokuments aus diese zu referenzieren: 1 <!DOCTYPE n o t e SYSTEM ” c o l l e c t i o n . dtd ”> 2.5 XML Verarbeitung 2.5.1 Das DOM Api Das DOM-API ist in der Standard-Javaentwicklungsumgebung implementiert. Hierzu findet sich im API das Paket org.w3c.dom. Es fällt auf, dass dieses Paket nur Schnittstellen und keine Klassen enthält. Der Grund hierfür ist, dass es sich bei DOM um ein implementierungsunabhängiges API handelt. So soll der Programmierer auch gar keine Klassen kennenlernen, die das API implementieren. Die wichtigsten Schnittstellen Die zentrale Schnittstelle in dom ist Node. Sie hat als Unterschnittstellen alle Knotentypen, die es in XML gibt. Folgende Graphik gibt über diese Knotentypen einen Überblick. 1 2 3 4 5 6 7 8 9 i n t e r f a c e o r g . w3c . dom . Node | |−− i n t e r f a c e o r g . w3c . dom . Attr |−− i n t e r f a c e o r g . w3c . dom . CharacterData | | | |−− i n t e r f a c e o r g . w3c . dom . Comment | |−− i n t e r f a c e o r g . w3c . dom . Text | | | |−− i n t e r f a c e o r g . w3c . dom . CDATASection 78 2.5 XML Verarbeitung 10 11 12 13 14 15 16 17 18 | |−− i n t e r f a c e |−− i n t e r f a c e |−− i n t e r f a c e |−− i n t e r f a c e |−− i n t e r f a c e |−− i n t e r f a c e |−− i n t e r f a c e |−− i n t e r f a c e o r g . w3c . dom . Document o r g . w3c . dom . DocumentFragment o r g . w3c . dom . DocumentType o r g . w3c . dom . Element o r g . w3c . dom . E n t i t y o r g . w3c . dom . E n t i t y R e f e r e n c e o r g . w3c . dom . N o t a t i o n o r g . w3c . dom . P r o c e s s i n g I n s t r u c t i o n Wie man sieht, findet sich die Struktur, die wir in unserem eigenen Versuch, ein XML-Baum-API zu entwickeln, entworfen haben, wieder. Die allgemeine Schnittstelle Node und deren beiden Unterschnittstellen Element und Text. Eine der entscheidenen Methoden der Schnittstelle Node selektiert die Liste der Kinder eines Knotens: public NodeList getChildNodes() Knoten, die keine Kinder haben können (Textknoten, Attribute etc.) geben bei dieser Methode die leere Liste zurück. Attribute zählen auch wie in unserer Modellierung nicht zu den Kindern eines Knotens. Um an die Attribute zu gelangen, gibt es eine eigene Methode: NamedNodeMap getAttributes() Wie man sieht, benutzt Javas dom Umsetzung keine von Javas Listenklassen zur Umsetzung einer Knotenliste, sondern nur genau die in DOM spezifizierte Schnittstelle NodeList. Eine NodeList hat genau zwei Methoden: 1 2 i n t getLe ngt h ( ) Node item ( i n t i n d e x ) Mit diesen beiden Methoden lässt sich über die Elemente einer Liste über den Indexzugriff iterieren. Die Schnittstelle NodeList implementiert nicht die Schnittstelle Iterable. Dieses ist insofern schade, da somit nicht die neue for-each-Schleife aus Java für die Knotenliste des DOM benutzt werden kann. Die Schnittstelle Node enthält ebenso die anderen Methoden, die wir uns vorher in der eigenen Umsetzung überlegt haben: 1 2 3 S t r i n g getNodeName ( ) ; S t r i n g getNodeValue ( ) ; s h o r t getNodeType ( ) ; 79 Kapitel 2 Hierarchische Strukturen Die Methode getNodeType() gibt keine Konstante einer Aufzählung zurück, sondern eine Zahl des Typs short, die den Knotentyp kodiert. In der Schnittstelle Node sind Konstanten für die einzelnen Knotentypen definiert. Die anderen beiden Methoden verhalten sich so, wie wir es in unserer Minimalumsetzung auch implementiert haben. Die Schnittstelle Node des DOM-APIs ist wesentlich umfangreicher als die kleine Schnittstelle Node, die wir uns zum Einstieg überlegt haben. Insbesondere gibt es eine Methode, mit der von einem Knoten auch wieder hoch zu seinem Elternknoten navigiert werden kann. 1 Node getParentNode ( ) Sofern ein Knoten einen Elternknoten besitzt, kann dieser mit dieser Methode erfragt werden. Ansonsten ist das Ergebnis null. Parser zur Erzeugung von DOM Objekten Wir benötigen einen Parser, der uns die Baumstruktur eines XML-Dokuments erzeugt. In der Javabibliothek ist ein solcher Parser integriert, allerdings nur über seine Schnittstellenbeschreibung. Im Paket javax.xml.parsers gibt es nur Schnittstellen. Um einen konkreten Parser zu erlangen, bedient man sich einer Fabrikmethode: In der Schnittstelle DocumentBuilderFactory gibt es eine statische Methode newInstance und über das DocumentBuilderFactory-Objekt, läßt sich mit der Methode newDocumentBuilder ein Parser erzeugen. Wir können so eine statischen Methode zum Parsen eines XML-Dokuments schreiben: 1 package name . p a n i t z . domtest ; 2 3 4 5 import o r g . w3c . dom . Document ; import j a v a x . xml . p a r s e r s . * ; import j a v a . i o . F i l e ; 6 7 8 9 10 11 12 13 14 15 16 p u b l i c c l a s s ParseXML { p u b l i c s t a t i c Document parseXml ( S t r i n g xmlFileName ) { try { return DocumentBuilderFactory . newInstance ( ) . newDocumentBuilder ( ) . p a r s e ( new F i l e ( xmlFileName ) ) ; } c a t c h ( E x c e p t i o n _) { r e t u r n n u l l ; } } 17 80 2.5 XML Verarbeitung p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { System . out . p r i n t l n ( parseXml ( a r g s [ 0 ] ) ) ; } 18 19 20 21 } Listing 2.50: ParseXML.java Wir können jetzt z.B. den Quelltext dieses Skripts parsen. sep@linux:~/fh/prog4/examples> java -classpath classes/ name.panitz.domtest.ParseXML ../skript.xml [#document: null] Wie man sieht ist die Methode toString in der implementierenden Klasse der Schnittstelle Document, die unser Parser benutzt nicht sehr aufschlußreich. Aufgabe 1 Im ersten Semester haben wir eine kleine Klasse für Datumsobjekte entwickelt. Im Java 8 gibt es es ein neues Paket java.time. Hier finden sich Klassen um Datums und Uhrzeitobjekte auszudrücken. a) Machen Sie sich mit dem neuen Api vertraut. Schreiben Sie ein kleines Tutorial von wenigen Seiten und bereiten einen 5-10 minütigen Vortrag darüber vor. In der Praktikumsstunde werden Kandidaten ausgelost, die den Vortrag halten sollen. Die beste Ausarbeitung soll als Kapitel in diesem Skript integriert werden. b) Schreiben Sie eine Klasse Agenda. Diese soll Einträge in einem Terminkalender darstellen. Sie ist eine Liste von Terminen. Ein Termin hat einen Zeitpunkt, eine Beschreibung, eine Dauer und einen Ort. Zusätzlich soll die Klasse die Möglichkeit haben, den Terminkalender als XML Dokument abzuspeichern und wieder zu lesen. Aufgabe 1 (Unbewertete Zusatzaufgabe) Schreiben Sie eine Klasse XMLIter, die die Schnittstelle Iterable implementiert. Sie soll im Konstruktor einen Dom-Knoten erhalten. Bei der Iteration sollen die Kindknoten des Dom-Knotens durchlaufen werden. 2.5.2 XPath Für Bäume stellen Pfade ein wichtiges Konzept dar. Entlang der Pfade navigiert man durch einen Baum. Anhand eines Pfades können Teilbäume selektiert werden. Deshalb hat das W3C eine Empfehlung heraus gegeben, mit deren Hilfe Pfade in XML-Dokumenten beschrieben werden können. Die Empfehlung für XPath. Eines der wichtigsten Konzepten von XPath sind die sogenannten Achsen. Sie beschreiben bestimmte Arten sich in einem Baum zu bewegen. XPath kennt 12 81 Kapitel 2 Hierarchische Strukturen Achsen. Eine Achse beschreibt ausgehend von einem Baumknoten eine Liste von Knoten, die diese Achse definiert. Die am häufigste verwendete Achse ist, die Kinderachse. Sie beschreibt die Liste aller direkten Kinder eines Knotens. Entsprechend gibt es die Elternachse, die den Elternknoten beschreibt. Diese Liste hat maximal einen Knoten. Eine sehr simple Achse beschreibt den Knoten selbst. Diese Achse hat immer eine einelementige Liste als Ergebnis. Achsen für Vorfahren und Nachkommen sind der transitive Abschluss der Eltern- bzw. Kinderachse. Zusätzlich gibt es noch Geschwisterachsen, eine für die Geschwister, die vor dem aktuellen Knoten stehen und einmal für die Geschwister, die nach dem aktuellen Knoten stehen. Eine eigene Achse steht für Attribute. Schließlich gibt es noch eine Achse für Namensraumdeklarationen. Mit einem Aufzählungstypen, lässt sich in Java einfach ein Typ, der alle 12 Achsen aufzählt, implementieren. 1 2 3 4 5 package name . p a n i t z . xml . xpath ; import name . p a n i t z . pmt . i t e r a t i o n . I n t e g e r R a n g e ; import j a v a . u t i l . L i s t ; import j a v a . u t i l . A r r a y L i s t ; import o r g . w3c . dom . * ; 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 p u b l i c enum Axes {self , child , descendant , descendant_or_self , parent , ancestor , ancestor_or_self , following_sibling , following , preceding_sibling , preceding , attribute ; Listing 2.51: Axes.java Für jede dieser Achsen soll in der Folge eine Implementierung auf DOM gegeben werden. Eine Achsenimplementierung entspricht dabei einer Funktion, die für einen Knoten eine Liste von Knoten zurück gibt. Da das DOM-API bereits eine eigene Listenimplementierung hat, wir aber bei den Achsen auf Java-Standardlisten zurückgreifen wollen, sei hier eine simple Konvertierungsmethode von org.w3c.dom.NodeList auf java.util.List gegeben. 22 23 p u b l i c s t a t i c L i s t <Node> n o d e l i s t T o L i s t ( NodeList n l ) { 82 2.5 XML Verarbeitung L i s t <Node> r e s u l t = new A r r a y L i s t <Node >() ; f o r ( i n t i : new I n t e g e r R a n g e ( 0 , n l . ge t L en g th ( ) −1) ) { r e s u l t . add ( n l . item ( i ) ) ; } return r e s u l t ; 24 25 26 27 28 29 } Listing 2.52: Axes.java Die Achse self Die einfachste Achse liefert genau den Knoten selbst. Die entsprechende Methode liefert die einelementige Liste des Eingabeknotens. Wir geben für jede Achse zwei Methoden, um deren Ergebnisliste zu erhalten. Die erste Methode erstellt die Ergebnisliste. 30 31 32 p u b l i c s t a t i c L i s t <Node> s e l f ( Node n ) { r e t u r n s e l f ( n , new A r r a y L i s t <Node >() ) ; } Listing 2.53: Axes.java Die zweite bekommt die Ergebnisliste als Parameter übergeben und fügt in diese die Knoten ein. 33 34 35 36 p u b l i c s t a t i c L i s t <Node> s e l f ( Node n , L i s t <Node> r e s u l t ) { r e s u l t . add ( n ) ; return r e s u l t ; } Listing 2.54: Axes.java Auf diese Weise kann verhindert werden, dass wenn die Vereinigung mehrerer Achsen berechnet werden soll, oder wenn die Berechnung einer Achse einen rekursive Methode darstellt, unnötig Zwischenlisten erzeugt werden. Die Achse child Die gebräuchlichste Achse beschreibt die Menge aller Kinderknoten. Wir lassen uns die Kinder des DOM Knotens geben und konvertieren die NodeList zu einer Javaliste. Auch hier werden wieder zwei Versionen der Methode definiert. Einmal wird eine Liste für das ergebnis neu erzeugt, im zweiten Fall wird eine Liste, die die Ergebnisknoten sammelt als Parameter übergeben. 83 Kapitel 2 Hierarchische Strukturen p u b l i c s t a t i c L i s t <Node> c h i l d ( Node n ) { r e t u r n c h i l d ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> c h i l d ( Node n , L i s t <Node> r e s u l t ) { NodeList n l = n . g e t C h i l d N o d e s ( ) ; f o r ( i n t i : new I n t e g e r R a n g e ( 0 , n l . get Le n gt h ( ) −1) ) { r e s u l t . add ( n l . item ( i ) ) ; } return r e s u l t ; } 37 38 39 40 41 42 43 44 45 46 Listing 2.55: Axes.java Die Achse descendant Die Achse der Nachkommen eines Knotens beschreibt die Liste aller Kinder, Kindeskinder usw. also aller Knoten, die Unterhalb des Ausgangsknotens liegen. Diese sollen in die Ergebnisliste in preorder eingefügt werden. p u b l i c s t a t i c L i s t <Node> d e s c e n d a n t ( Node n ) { r e t u r n d e s c e n d a n t ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> d e s c e n d a n t ( Node n , L i s t <Node> r e s u l t ) { f o r ( Node c h i l d : c h i l d ( n ) ) { r e s u l t . add ( c h i l d ) ; descendant ( child , r e s u l t ) ; } return r e s u l t ; } 47 48 49 50 51 52 53 54 55 56 Listing 2.56: Axes.java Die Achse descendant-or-self In der Nachkommenachse taucht der Knoten selbst nicht auf. Diese Achse fügt den Knoten selbst zusätzlich vorne ans Ergebnis an. Es ist also die Vereinigung der Achse self mit der Achse descendant. p u b l i c s t a t i c L i s t <Node> d e s c e n d a n t O r S e l f ( Node n ) { r e t u r n d e s c e n d a n t O r S e l f ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> d e s c e n d a n t O r S e l f ( Node n , L i s t <Node> r e s u l t ) { descendant (n , r e s u l t ) ; r e s u l t . add ( 0 , n ) ; return r e s u l t ; } 57 58 59 60 61 62 63 64 84 2.5 XML Verarbeitung Listing 2.57: Axes.java Die Achse parent Die Kinderachse lief in den Baum Richtung Blätter eine Ebene nach unten, die Elternachse läuft diese eine Ebene Richtung Wurzel nach oben. Die Ergebnisliste hat maximal ein Element, eventuell 0 Elemente, wenn der Knoten selbst als Elternknoten null zurück gibt. 65 66 67 68 69 70 71 72 73 p u b l i c s t a t i c L i s t <Node> p a r e n t ( Node n ) { r e t u r n p a r e n t ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> p a r e n t ( Node n , L i s t <Node> r e s u l t ) { Node pa = n . getParentNode ( ) ; i f ( pa!= n u l l && pa . getNodeType ( ) !=Node .DOCUMENT_NODE) r e s u l t . add ( pa ) ; return r e s u l t ; } Listing 2.58: Axes.java Die Achse ancestor Die Vorfahrenachse ist der transitive Abschluß über die Elternachse, sie bezeichnet also die Eltern und deren Vorfahren. Diese Achse enthält also auf jeden Fall den Wurzelknoten. Sie enthält tatsächlich den Pfad von der Wurzel zum Ausgangsknoten, allerdings ohne den Ausgangsknoten. Dieser ist erst in der Achse ancestor-or-self des nächsten Punktes enthalten. 74 75 76 77 78 79 80 81 82 83 p u b l i c s t a t i c L i s t <Node> a n c e s t o r ( Node n ) { r e t u r n a n c e s t o r ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> a n c e s t o r ( Node n , L i s t <Node> r e s u l t ) { f o r ( Node pa : p a r e n t ( n ) ) { r e s u l t . add ( pa ) ; a n c e s t o r ( pa , r e s u l t ) ; } return r e s u l t ; } Listing 2.59: Axes.java 85 Kapitel 2 Hierarchische Strukturen Die Achse ancestor-or-self Ebenso wie für die Achse aller Nachkommen ist auch für die Achse aller Vorfahren eine Version definiert, in der der Ausgangsknoten selbst noch enthalten ist. p u b l i c s t a t i c L i s t <Node> a n c e s t o r O r S e l f ( Node n ) { r e t u r n a n c e s t o r O r S e l f ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> a n c e s t o r O r S e l f ( Node n , L i s t <Node> r e s u l t ) { ancestor (n , r e s u l t ) ; r e s u l t . add ( n ) ; return r e s u l t ; } 84 85 86 87 88 89 90 91 Listing 2.60: Axes.java Die Achse following-sibling Auch für die Geschwister eines Knotens sind Achsen definiert. Geschwister sind Knoten, die denselben Elternknoten haben. Die following-sibling-Achse definiert alle Geschwister, die in der Kinderliste meines Elternknotens nach mir kommen. Sozusagen alle jüngeren Geschwister. p u b l i c s t a t i c L i s t <Node> f o l l o w i n g S i b l i n g ( Node n ) { r e t u r n f o l l o w i n g S i b l i n g ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> f o l l o w i n g S i b l i n g ( Node n , L i s t <Node> r e s u l t ) { Node s i b = n . g e t N e x t S i b l i n g ( ) ; w h i l e ( s i b != n u l l ) { r e s u l t . add ( s i b ) ; sib = sib . getNextSibling () ; } return r e s u l t ; } 92 93 94 95 96 97 98 99 100 101 102 Listing 2.61: Axes.java Die Achse preceding-sibling Die preceding-sibling-Achse definiert alle Geschwister, die in der Kinderliste meines Elternknotens vor mir kommen. Sozusagen alle älteren Geschwister. p u b l i c s t a t i c L i s t <Node> p r e c e d i n g S i b l i n g ( Node n ) { r e t u r n p r e c e d i n g S i b l i n g ( n , new A r r a y L i s t <Node >() ) ; } 103 104 105 86 2.5 XML Verarbeitung 106 107 108 109 110 111 112 113 p u b l i c s t a t i c L i s t <Node> p r e c e d i n g S i b l i n g ( Node n , L i s t <Node> r e s u l t ) { Node s i b = n . g e t P r e v i o u s S i b l i n g ( ) ; w h i l e ( s i b != n u l l ) { r e s u l t . add ( s i b ) ; sib = sib . getPreviousSibling () ; } return r e s u l t ; } Listing 2.62: Axes.java Die Achse following Die Achse following bezieht sich auf die following-sibling-Achse. Sie bezeichnet alle following-sibling-Knoten und deren Nachkommen, sowie dann noch alle weiteren following-siblings der Vorfahren und wieder deren Nachkommen. Textuell bezeichnet diese Achse alle Knoten des XML-Baums, die beginnen, nachdem der Ausgangsknoten beendet ist. Daher auch der Name, alle Knoten die folgen. Mathematisch lässt sich die Achse wie folgt beschreiben: following(n) = {f |a ∈ ancestor-or-self(n), s ∈ following-sibling(a), f ∈ descendant-or-self(s)} 114 115 116 117 118 119 120 121 122 123 p u b l i c s t a t i c L i s t <Node> f o l l o w i n g ( Node n ) { r e t u r n f o l l o w i n g ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> f o l l o w i n g ( Node n , L i s t <Node> r e s u l t ) { f o r ( Node a : a n c e s t o r O r S e l f ( n ) ) f o r ( Node s : f o l l o w i n g S i b l i n g ( a ) ) f o r ( Node f : d e s c e n d a n t O r S e l f ( s ) ) r e s u l t . add ( f ) ; return r e s u l t ; } Listing 2.63: Axes.java Es werden also von allen Vorfahren und dem Knoten selbst die nachfolgenden Geschwister berechnet und diese und deren Nachkommen genommen. Die Achse preceding Entsprechend bezeichnet die Achse preceding von allen Vorfahren die precedingsibling-Knoten und deren Nachkommen. Dieses bedeutet, dass genau die Knoten beschrieben werden, die in der textuellen Darstellung beendet wurden, bevor der Ausgangsknoten begonnen hat.. 87 Kapitel 2 Hierarchische Strukturen preceding(n) = {f |a ∈ ancestor-or-self(n), s ∈ preceding-sibling(a), f ∈ descendant-or-self(s)} Es werden also von allen Vorfahren und dem Knoten selbst die vorhergehenden Geschwister berechnet und diese und deren Nachkommen genommen. p u b l i c s t a t i c L i s t <Node> p r e c e d i n g ( Node n ) { r e t u r n p r e c e d i n g ( n , new A r r a y L i s t <Node >() ) ; } p u b l i c s t a t i c L i s t <Node> p r e c e d i n g ( Node n , L i s t <Node> r e s u l t ) { f o r ( Node a : a n c e s t o r O r S e l f ( n ) ) f o r ( Node s : p r e c e d i n g S i b l i n g ( a ) ) f o r ( Node f : d e s c e n d a n t O r S e l f ( s ) ) r e s u l t . add ( f ) ; return r e s u l t ; } 124 125 126 127 128 129 130 131 132 133 Listing 2.64: Axes.java Damit sind alle Achsen beschrieben, sie sich auf die Baumstruktur des Dokuments beziehen. Die noch ausbleibende Achse bezieht sich auf die Attribute eines XMLDokuments, die wir nicht als Teil der Baumstruktur gesehen haben. Alle Knoten eines Dokuments lassen sich durch die Vereinigung der Achsen preceding, ancestor, self, descendant und followong selektieren. Man kann auch als Faustregel sagen, es sind die vier Richtungen, die von einem Baumknoten beschritten werden können: links, über , unter und rechts. preceding sind die Knoten links von mir, ancestor sind die Knoten über mir, descendant sind die Knoten unter mir und following die Knoten rechts von mir. Die Achse attribute Die bisherigen Achsen selektierten Knoten aus der Baumhierarchie eines XMLDokuments. Die letzten beiden Achsen beziehen sich auf die Attribute. Diese sind nicht teil der Kindknoten eines Elements. Daher werden über die bisherigen Achsen niemals Attribute selektiert. Hierfür gibt es die besondere Achse attribute, die die Liste aller Attribute eines Knoten selektiert. p u b l i c s t a t i c L i s t <Node> a t t r i b u t e ( Node n ) { L i s t <Node> r e s u l t = new A r r a y L i s t <Node >() ; NamedNodeMap n l = n . g e t A t t r i b u t e s ( ) ; i f ( n u l l != n l ) { f o r ( i n t i : new I n t e g e r R a n g e ( 0 , n l . g etL e ng t h ( ) −1) ) { r e s u l t . add ( n l . item ( i ) ) ; } } return r e s u l t ; 134 135 136 137 138 139 140 141 142 88 2.5 XML Verarbeitung } 143 144 p u b l i c L i s t <Node> s e l e c t ( Node n ) { switch ( t h i s ) { case child : return child (n) ; case descendant : return descendant (n) ; case descendant_or_self : return descendantOrSelf (n) ; case parent : return parent (n) ; case ancestor : return ancestor (n) ; case ancestor_or_self : return ancestorOrSelf (n) ; case following_sibling : return followingSibling (n) ; case preceding_sibling : return precedingSibling (n) ; case following : return following (n) ; case preceding : return preceding (n) ; case attribute : return attribute (n) ; default : return s e l f (n) ; } } 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 } Listing 2.65: Axes.java Knotentest Die Kernausdrücke in XPath sind von der Form: axis::nodeTest axis beschreibt dabei eine der 12 Achsen. nodeTest ermöglicht es, aus den durch die Achse beschriebenen Knoten bestimmte Knoten zu selektieren. Syntaktisch gibt es 8 Arten des Knotentests: • *: beschreibt Elementknoten mit beliebigen Namen. • pref:*: beschreibt Elementknoten mit dem Prefix pref und beliebigen weiteren Namen. • pref:name: beschreibt Elementknoten mit dem Prefix pref und den weiteren Namen name. Der Teil vor den Namen kann dabei auch fehlen. • comment(): beschreibt Kommentarknoten. • text(): beschreibt Textknoten. • processing-instruction(): beschreibt Processing-Instruction-Knoten. • processing-instruction(target): beschreibt Processing-Instruction-Knoten mit einem bestimmten Ziel. • node(): beschreibt beliebige Knoten. Ein XPath-Ausdruck bezieht sich immer auf einen Ausgabgsknoten im Dokument. Die Bedeutung eines XPath-Ausdrucks 89 Kapitel 2 Hierarchische Strukturen axisType::nodeTest ist dann: Selektiere vom Ausgangsknoten alle Knoten mit der durch axisType angegebenen Achse. Von dieser Liste selektiere dann alle Knoten, für die der Knotentest erfolgreich ist. Der folgende Ausdruck selektiert alle Nachkommen des Ausgangsknoten, die den Elementknoten mit den Tagnamen code sind: descendant-or-self::code Dieses sind in dem XML-Dokument, das dieses Skript beschreibt gerade alle Teile, in denen Java Quelltext steht. Mehrere XPath-Ausdrücke können laut der XPath Definition nacheinander ausgeführt werden. Hierzu schreibt man zwischen den einzelnen XPath-Audrücken einen Schrägstrich. Der so zusammengesetzte Ausdruck selektiert wieder eine Liste von Knoten ausgehend von einem Ausgangsknoten. Die Bedeutung von einer solchen Verkettung von XPath-Ausdrücken ist: Selektiere erst mit dem ersten XPath-Teilausdruck ausgehend von einem Startknoten die Liste der Ergebnisknoten. Dann nehme jeden der Knoten in dieser Ergebnisliste als neuen Ausgangsknoten für den zweiten XPath-Ausdruck und vereinige deren Ergebnissse. Für das Beispiel des XML-Dokuments dieses Skriptes bezeichnet der Ausdruck child::kapitel/child::section/descendant::code/child:text() Den Text, der direkt unterhalb eines code Elements in steht, wobei das code Element Nachfahre eines section Elements ist, welches direktes Kind eines kapitel Elements ist, welches wiederum ein Kind des Ausgangsknotens ist. Implementierung Nachdem wir bereits alle Achsen selbst implementiert haben, sollen auch die Knotentests implementiert werden. Hierzu können wir eine kleine Schnittstelle vorsehen, die einen Knotentest ausdrückt. Diese ist nichts weiter als ein für Knoten spezialisiertes Prädikat, so dass wir die allgemeine Schnittstelle Predicate erweitern können 1 2 3 4 package name . p a n i t z . xml . xpath ; import o r g . w3c . dom . Node ; import o r g . w3c . dom . Text ; import j a v a . u t i l . f u n c t i o n . P r e d i c a t e ; 5 6 p u b l i c i n t e r f a c e NodeTest e x t e n d s P r e d i c a t e <Node>{ Listing 2.66: NodeTest.java 90 2.5 XML Verarbeitung Die einfachen Tests, ob es sich um Text, Kommentar oder einen beliebigen Knoten handelt, lassen sich über einen Lambda-Ausdruck definieren. s t a t i c f i n a l NodeTest commentTest = n−>n . getNodeType ( ) == Node . COMMENT_NODE; s t a t i c f i n a l NodeTest t e x t T e s t = n−>n i n s t a n c e o f Text ; s t a t i c f i n a l NodeTest someNodeTest = n−>t r u e ; 7 8 9 10 } Listing 2.67: NodeTest.java Für die weiteren Knotentest können Implementierungen dieser Schnittstelle vorgesehen werden. Die erste Klasse vereinigt die drei Knotentest, die sich auf die Elementknoten beziehen: 1 2 package name . p a n i t z . xml . xpath ; import o r g . w3c . dom . Node ; 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 p u b l i c c l a s s NameTest implements NodeTest { String prefix ; S t r i n g name ; p u b l i c NameTest ( S t r i n g p r e f i x , S t r i n g name ) { this . prefix = prefix ; t h i s . name = name ; } p u b l i c NameTest ( S t r i n g name ) { this . prefix = null ; t h i s . name = name ; } p u b l i c NameTest ( ) { this . prefix = null ; t h i s . name = n u l l ; } 19 p u b l i c b o o l e a n t e s t ( Node n ) { i f ( n . getNodeType ( ) != Node .ELEMENT_NODE && n . getNodeType ( ) != Node .ATTRIBUTE_NODE) r e t u r n f a l s e ; 20 21 22 23 i f ( n u l l ==p r e f i x && n u l l==name ) r e t u r n t r u e ; i f ( n u l l==p r e f i x ) r e t u r n n . getNodeName ( ) . e q u a l s ( name ) ; i f ( n u l l==name ) r e t u r n n . g e t P r e f i x ( ) . e q u a l s ( p r e f i x ) ; r e t u r n n . g e t P r e f i x ( ) . e q u a l s ( p r e f i x ) && n . getLocalName ( ) . e q u a l s ( name ) ; 24 25 26 27 } @Override p u b l i c S t r i n g t o S t r i n g ( ) { i f ( n u l l ==p r e f i x && n u l l==name ) r e t u r n ” * ” ; i f ( n u l l==p r e f i x ) r e t u r n name ; i f ( n u l l==name ) r e t u r n p r e f i x+” : * ” ; r e t u r n p r e f i x+” : ”+name ; } 28 29 30 31 32 33 34 35 } 91 Kapitel 2 Hierarchische Strukturen Listing 2.68: NameTest.java Die folgende Klasse realisiert die Knotentests, die sich auf precessing instructions beziehen. 1 2 3 package name . p a n i t z . xml . xpath ; import o r g . w3c . dom . Node ; import o r g . w3c . dom . P r o c e s s i n g I n s t r u c t i o n ; 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 p u b l i c c l a s s P r o c e s s i n g I n s t r u c t i o n T e s t implements NodeTest { String target ; public ProcessingInstructionTest () { target = null ; } public ProcessingInstructionTest ( String target ){ this . target = target ; } p u b l i c b o o l e a n t e s t ( Node n ) { i f ( n . getNodeType ( ) != Node .PROCESSING_INSTRUCTION_NODE) r e t u r n false ; i f ( n u l l != t a r g e t ) { return target . equals ( ( ( Pr oc es si n gI ns tr uc t io n )n) . getTarget () ) ; } return true ; } @Override p u b l i c S t r i n g t o S t r i n g ( ) { i f ( n u l l==t a r g e t ) r e t u r n ” p r o c e s s i n g −i n s t r u c t i o n ( ) ” ; r e t u r n ” p r o c e s s i n g −i n s t r u c t i o n ( ”+t a r g e t+” ) ” ; } } Listing 2.69: ProcessingInstructionTest.java Mit den Achsen von XPath und den Knotentests, lassen sich nun XPath-Ausdrücke als Paar von einer Achse und einem Test darstellen. 1 2 3 4 package name . p a n i t z . xml . xpath ; import o r g . w3c . dom . Node ; import j a v a . u t i l . L i s t ; import j a v a . u t i l . A r r a y L i s t ; 5 6 7 8 p u b l i c c l a s s XPathExpr{ Axes a x i s ; NodeTest t e s t ; 9 p u b l i c XPathExpr ( Axes a x i s , NodeTest t e s t ) { this . axis = axis ; this . test = test ; } 10 11 12 13 92 2.5 XML Verarbeitung p u b l i c L i s t <Node> s e l e c t ( Node n ) { L i s t <Node> r e s u l t = new A r r a y L i s t <>() ; f o r ( Node x : a x i s . s e l e c t ( n ) ) { i f ( t e s t . t e s t ( x ) ) r e s u l t . add ( x ) ; } return r e s u l t ; } @Override p u b l i c S t r i n g t o S t r i n g ( ) { r e t u r n a x i s+” : : ”+t e s t ; } 14 15 16 17 18 19 20 21 22 23 24 } Listing 2.70: XPathExpr.java Ein ganzer XPath-Ausdruck stellt eine Folge von einfachen Ausdrücken da, die nacheinander zur Selektion benutzt werden. 1 2 3 4 package name . p a n i t z . xml . xpath ; import o r g . w3c . dom . Node ; import j a v a . u t i l . L i s t ; import j a v a . u t i l . A r r a y L i s t ; 5 6 7 8 9 10 11 12 13 14 15 16 17 18 p u b l i c c l a s s XPath e x t e n d s A r r a y L i s t <XPathExpr>{ p u b l i c L i s t <Node> s e l e c t ( Node n ) { L i s t <Node> r e s u l t = new A r r a y L i s t <>() ; r e s u l t . add ( n ) ; f o r ( XPathExpr xpath : t h i s ) { L i s t <Node> r e s u l t 2 = new A r r a y L i s t <>() ; f o r ( Node x : r e s u l t ) { r e s u l t 2 . addAll ( xpath . s e l e c t ( x ) ) ; } result = result2 ; } return r e s u l t ; } 19 @Override p u b l i c S t r i n g t o S t r i n g ( ) { S t r i n g B u f f e r r e s u l t = new S t r i n g B u f f e r ( ) ; boolean f i r s t = true ; f o r ( XPathExpr xpath : t h i s ) { i f ( f i r s t ){ first = false ; }else { r e s u l t . append ( ” / ” ) ; } r e s u l t . append ( xpath . t o S t r i n g ( ) ) ; } return r e s u l t . toString () ; } 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 } 93 Kapitel 2 Hierarchische Strukturen Listing 2.71: XPath.java Aufgabe 1 Experimentieren Sie mit der kleinen XPath-Implementierung dieses Kapitels. Benutzen Sie dazu das http://www.cs.hsrm.de/ panitz/java/skript.xmlXML-Dokument des Quelltextes vom kompletten Java-Skriptes von der Webseite. Schreiben Sie hierzu ein Testprogramm, das aus den Elementen anhang alle code Nachkommen und davon alle Text-Nachkommen selektiert und deren Values schließlich auf der Kommandozeile ausgibt. Es soll also folgender Ausdruck auf das Wurzelelement ausgeführt werden: ./anhang//code//text(). Abkürzende Schreibweise In obiger Einführung haben wir bereits gesehen, dass XPath den aus dem Dateissystem bekannten Schrägstrich für Pfadangaben benutzt. Betrachten wir XPathAusdrücke als Terme, so stellt der Schrägstrich einen Operator dar. Diesen Operator gibt es sowohl einstellig wie auch zweistellig. Die Grundsyntax in XPath sind eine durch Schrägstrich getrennte Folge von Kernausdrücke mit Achsentyp und Knotentest. Der Ausdruck child::skript/child::*/descendant::node()/self::code/attribute::class beschreibt die Attribute mit Attributnamen class, die an einem Elementknoten mit Tagnamen code hängen, die Nachkommen eines beliebigen Elementknotens sind, die Kind eines Elementknotens mit Tagnamen skript sind, die Kind des aktuellen Knotens, auf dem der Ausdruck angewendet werden soll sind. Vorwärts gelesen ist dieser Ausdruck eine Selektionsanweisung: Nehme alle skript-Kinder des aktuellen Knotens. Nehme von diesen beliebige Kinder. Nehme von diesen alle code-Nachkommen. Und nehme von diesen jeweils alle class-Attribute. Der einstellige Schrägstrichoperator bezieht sich auf die Dokumentwurzel des aktuellen Knotens. Aufgabe 1 Angewandte Informatik. Abgabe in der Woche vom 16.6. Implementieren Sie die Funktionalität der Header-Datei ArrayList.h gemäß der dokumentierten Spezifikation. (http://www.cs.hs-rm.de/ panitz//pmt/blatt7/ArrayList.h) 94 2.5 XML Verarbeitung Eine kleines Programm mit Beispielaufrufen steht Ihnen zur Verfügung: TestArrayList.c. (http://www.cs.hs-rm.de/ panitz/pmt/blatt7/TestArrayList.c) Zusätzlich steht Ihnen die Dokumentation im html-Format zur Verfügung. (http://www.cs.hs-rm.de/ panitz/pmt/blatt7/html/index.html) Der doppelte Schrägstrich XPath kennt einen zweiten Pfadoperator //. Auch er existiert jeweils einmal einstellig und einmal zweistellig. Der doppelte Schrägstrich ist eine abkürzende Schreibweise, die übersetzt werden kann in Pfade mit einfachen Schrägstrichoperator. • //expr: Betrachte beliebige Knoten unterhalt des Dokumentknotens, die durch expr charakterisiert werden. • e1 //e2 : Betrachte beliebiege Knoten unterhalb der durch e1 charkterisierten Knoten.und prüfe diese auf e2 . Der Doppelschrägstrich ist /descendant-or-self::node()/ eine abkürzende Schreibweise für: Der Punkt Für die Selbstachse kann als abkürzende Schreibweise ein einfacher Punkt . gewählt werden, wie er aus den Pfadangaben im Dateisystem bekannt ist. Der Punkt . ist die abkürzende Schreibweise für: self::node(). Der doppelte Punkt Für die Elternachse kann als abkürzende Schreibweise ein doppelter Punkt .. gewählt werden, wie er aus den Pfadangaben im Dateisystem bekannt ist. Der Punkt .. ist die abkürzende Schreibweise für: parent::node(). Vereinfachte Kindachse Ein Kernausdruck, der Form child::nodeTest kann abgekürzt werden durch nodeTest. Die Kinderachse ist also der Standardfall. Vereinfachte Attributachse Auch für die Attributachse gibt es eine abkürzende Schreibweise: ein Kernausdruck, der Form attribute::pre:name kann abgekürzt werden durch @pre:name. Insgesamt läßt sich der obige Ausdruck abkürzen zu: skript/*//code/@class 95 Kapitel 2 Hierarchische Strukturen XPath-Auswertung in Java Im Java-API ist eine XPath-Implementierung enthalten. Die dazugehörigen Klassen befinden sich im Paket javax.xml.xpath. Die Implementierung basiert auf DOM, es werden also DOM-Knoten als Eingabe und genommen und auch u.a. eine DOMNodelist als Ergebnis berechnet. Im Folgendem ein Beispiel, wie ein XPath-Ausdruck, der eine Knotenliste als Ergebnis hat, mit dem Java-API für XPath angewendet wird. Wir schreiben eine statische Methode, die einen XPath-Ausdruck als String übergeben bekommt und das Eingabedokument für diesen Ausdruck als einen Reader. 1 package name . p a n i t z . xml ; 2 3 4 5 import j a v a . i o . FileNotFoundException ; import j a v a . i o . F i l e R e a d e r ; import j a v a . i o . Reader ; 6 7 8 9 10 import import import import j a v a x . xml . xpath . XPath ; j a v a x . xml . xpath . XPathConstants ; j a v a x . xml . xpath . XPathExpressionException ; j a v a x . xml . xpath . XPathFactory ; 11 12 13 import o r g . w3c . dom . NodeList ; import o r g . xml . sax . I n p u t S o u r c e ; 14 15 16 17 p u b l i c c l a s s XPathTest { p r i v a t e s t a t i c NodeList evalXPath ( S t r i n g x p a t h E x p r e s s i o n , Reader reader ) throws XPathExpressionException { Listing 2.72: XPathTest.java Zunächst einmal benutzt das XPath-API wieder das Factory-Pattern, so dass ein XPath-Ausdruck nicht direkt durch einem Konstruktoraufruf erzeugt wird, sondern durch eine Methode in einer sogenannten Factory-Klasse, ebenso wie auch der DOMParser erzeut wurde. XPathFactory f a c t = XPathFactory . n e w I n s t a n c e ( ) ; XPath xpath = f a c t . newXPath ( ) ; 18 19 Listing 2.73: XPathTest.java Die Schnittstelle XPath kennt eine Methode evaluate, die drei Parameter hat: • der auszuwertende XPath-Ausdruck als String. • die Eingabequelle für das XML-Dokuement, auf dem dieser Ausdruck ausgewertet werden soll. 96 2.5 XML Verarbeitung • eine Konstante, die das erwartete Ergebnis anzeigt. Für die meisten XPathAusdrücke wird ein Objekt des Typs Nodelist als Ergebnis erwartet. Es gibt aber auch XPath-Ausdrücke, die nur Strings, oder Zahlen als Ergebnis haben. Das Ergebnis der Methode evaluate ist nur vom Typ Object und muss entsprechend erst noch mit einer Typzusicherung auf den eigentlichen Ergebnistypen überprüft werden. In unserem Beispiel gehen wir davon aus, dass das Ergebnis eine Nodelist ist. Falls dieses nicht der Fall ist, wird eine Ausnahme geworfen. NodeList ns = ( NodeList ) xpath . e v a l u a t e ( x p a t h E x p r e s s i o n , new I n p u t S o u r c e ( r e a d e r ) , XPathConstants .NODESET) ; r e t u r n ns ; 20 21 22 } 23 Listing 2.74: XPathTest.java Abschließend ein kleiner Testaufruf dieser Methode, der aus der XML-Datei, die diesem Skript zugrunde liegt, alle code-Elemente selektiert: p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws XPathExpressionException , FileNotFoundException { S t r i n g x p a t h E x p r e s s i o n = ” // code ” ; Reader r e a d e r = new F i l e R e a d e r ( ” s k r i p t . xml ” ) ; 24 25 26 27 28 NodeList ns = evalXPath ( x p a t h E x p r e s s i o n , r e a d e r ) ; f o r ( i n t i = 0 ; i < ns . g et L eng t h ( ) ; i ++) { System . out . p r i n t l n ( ns . item ( i ) . getTextContent ( ) ) ; } 29 30 31 32 } 33 34 } Listing 2.75: XPathTest.java 2.5.3 XSLT XML-Dokumente enthalten keinerlei Information darüber, wie sie visualisiert werden sollen. Hierzu kann man getrennt von seinem XML-Dokument ein sogenanntes Stylesheet schreiben. XSL ist eine Sprache zum Schreiben von Stylesheets für XMLDokumente. XSL ist in gewisser Weise eine Programmiersprache, deren Programme eine ganz bestimmte Aufgabe haben: XML Dokumente in andere XML-Dokumente zu transformieren. Die häufigste Anwendung von XSL dürfte sein, XML-Dokumente in HTML-Dokumente umzuwandeln. Wir werden die wichtigsten XSLT-Konstrukte mit folgendem kleinem XML Dokument ausprobieren: 97 Kapitel 2 Hierarchische Strukturen 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml v e r s i o n=” 1 . 0 ” e n c o d i n g=” i s o −8859−1” ?> <?xml−s t y l e s h e e t type=” t e x t / x s l ” h r e f=” cdTable . x s l ”?> <cds> <cd> <a r t i s t >The B e a t l e s </ a r t i s t > < t i t l e >White Album</ t i t l e > <l a b e l >Apple</ l a b e l > </cd> <cd> <a r t i s t >The B e a t l e s </ a r t i s t > < t i t l e >Rubber Soul </ t i t l e > <l a b e l >Parlophone </ l a b e l > </cd> <cd> <a r t i s t >Duran Duran</ a r t i s t > < t i t l e >Rio</ t i t l e > <l a b e l >T r i t e c </ l a b e l > </cd> <cd> <a r t i s t >Depeche Mode</ a r t i s t > < t i t l e >C o n s t r u c t i o n Time Again</ t i t l e > <l a b e l >Mute</ l a b e l > </cd> <cd> <a r t i s t >Yazoo</ a r t i s t > < t i t l e >U p s t a i r s a t E r i c s </ t i t l e > <l a b e l >Mute</ l a b e l > </cd> <cd> <a r t i s t >Marc Almond</ a r t i s t > < t i t l e >Absinthe </ t i t l e > <l a b e l >Some B i z a r r e </ l a b e l > </cd> <cd> <a r t i s t >ABC</ a r t i s t > < t i t l e >Beauty Stab </ t i t l e > <l a b e l >Mercury</ l a b e l > </cd> </cds> Gesamtstruktur XSLT-Skripte sind syntaktisch auch wieder XML-Dokumente. Ein XSLT-Skript hat feste Tagnamen, die eine Bedeutung für den XSLT-Prozessor haben. Diese Tagnamen haben einen festen definierten Namensraum. Das äußerste Element eines XSLT-Skripts hat den Tagnamen stylesheet. Damit hat ein XSLT-Skript einen Rahmen der folgenden Form: 98 2.5 XML Verarbeitung 1 2 3 4 5 <?xml v e r s i o n=” 1 . 0 ” e n c o d i n g=” i s o −8859−1” ?> <x s l : s t y l e s h e e t v e r s i o n=” 1 . 0 ” xmlns : x s l=” h t t p : / /www. w3 . o r g /1999/XSL/ Transform ”> </ x s l : s t y l e s h e e t > Wir können mit einer Processing-Instruction am Anfang eines XML-Dokumentes definieren, mit welchem XSLT Stylesheet es zu bearbeiten ist. Hierzu wird als Referenz im Attribut href die XSLT-Datei angegeben. 1 2 3 4 <?xml v e r s i o n=” 1 . 0 ” e n c o d i n g=” i s o −8859−1” ?> <?xml−s t y l e s h e e t type=” t e x t / x s l ” h r e f=” cdTable . x s l ”?> <cds> <cd > . . . . . . . . Vorlagen (templates) Das wichtigste Element in einem XSLT-Skript ist das Element xsl:template. In ihm wird definiert, wie ein bestimmtes Element transformiert werden soll. Es hat schematisch folgende Form: <xsl:template match="Elementname"> zu erzeugender Code </xsl:template > Folgendes XSLT-Skript transformiert die XML-Datei mit den CDs in eine HTMLTabelle, in der die CDs tabellarisch aufgelistet sind. 1 2 3 4 <?xml v e r s i o n=” 1 . 0 ” e n c o d i n g=” i s o −8859−1” ?> <x s l : s t y l e s h e e t v e r s i o n=” 1 . 0 ” xmlns : x s l=” h t t p : / /www. w3 . o r g /1999/XSL/ Transform ”> 5 6 7 8 9 10 11 12 <!−− S t a r t r e g e l f ü r das ganze Dokument . −−> <x s l : t e m p l a t e match=” / ”> <html><head><t i t l e >CD T a b e l l e </ t i t l e ></head> <body> <x s l : apply−t e m p l a t e s/> </body> </html> 99 Kapitel 2 Hierarchische Strukturen 13 </ x s l : template > 14 15 16 17 18 19 20 21 22 <!−− Reg el f ü r d i e CD−L i s t e . −−> <x s l : t e m p l a t e match=” c d s ”> <t a b l e b o r d e r=” 1 ”> <t r ><td><b>I n t e r p r e t </b></td><td><b>T i t e l </b></td></t r > <x s l : apply−t e m p l a t e s/> </t a b l e > </ x s l : template > 23 24 25 26 <x s l : t e m p l a t e match=” cd ”> <t r ><x s l : apply−t e m p l a t e s/></t r > </ x s l : template > 27 28 29 30 <x s l : t e m p l a t e match=” a r t i s t ”> <td><x s l : apply−t e m p l a t e s/></td> </ x s l : template > 31 32 33 34 <x s l : t e m p l a t e match=” t i t l e ”> <td><x s l : apply−t e m p l a t e s/></td> </ x s l : template > 35 36 37 38 39 <!−− Reg el f ü r a l l e ü b r i g e n Elemente . Mit d i e s e n s o l l n i c h t s gemacht werden −−> <x s l : t e m p l a t e match=” * ”> </ x s l : template > 40 41 </ x s l : s t y l e s h e e t > Öffnen wir nun die XML Datei, die unsere CD-Liste enthält im Webbrowser, so wendet er die Regeln des referenzierten XSLT-Skriptes an und zeigt die so generierte Webseite als Tabelle an. Läßt man sich vom Browser hingegen den Quelltest der Seite anzeigen, so wird kein HTML-Code angezeigt sondern der XML-Code. Aufgabe 2 Schreiben Sie ein XSL-Skript, das für die in der ersten Aufgabe angegebenen XML Quelltextdatei ein HTML-Dokument erzeugt, in dem alle Klassen des Skriptes mit Paketangabe aufgelistet sind. Die Klassen befinden sich dabei in den code Elementen. Dort gibt es Attribute für die Klasse und für das Paket. XSLT in Java Ebenso wie für XPath gibt es auch eine Implementierung von XSLT in Java. Die entsprechenden Klassen befinden sich im Paket javax.xml.transform. Auch hier wird wieder das Factory-Pattern angewendet. Wir geben im Folgendem eine kleine 100 2.5 XML Verarbeitung Beispielanwendung, in der ein XSLT-Skript auf ein XML-Dokument angewendet wird. Interessant ist, dass es auch möglich ist eine Instanz vom Typ Transformer ohne Angabe eines XSL-Skripts als Quelle zu erzeugen. Dieses entspricht dann der Identität, dass heißt, das transformierte Ausgabeausgabedokument ist gleich dem zu transformierenden Eingabedokument. Damit kann dieser Identittäts-Transformer dazu genutzt werden, um einen DOM-Baum wieder als textuelle Datei zu speichern. 1 package name . p a n i t z . xml . x s l t ; 2 3 import o r g . w3c . dom . Node ; 4 5 6 7 8 9 10 11 12 import import import import import import import import j a v a x . xml . t r a n s f o r m . T r a n s f o r m e r F a c t o r y ; j a v a x . xml . t r a n s f o r m . Transformer ; j a v a x . xml . t r a n s f o r m . OutputKeys ; j a v a x . xml . t r a n s f o r m . dom . DOMSource ; j a v a x . xml . t r a n s f o r m . S o u r c e ; j a v a x . xml . t r a n s f o r m . stream . StreamRes ult ; j a v a x . xml . t r a n s f o r m . stream . StreamSource ; j a v a x . xml . t r a n s f o r m . T r a n s f o r m e r E x c e p t i o n ; 13 14 15 import j a v a . i o . S t r i n g W r i t e r ; import j a v a . i o . F i l e ; 16 17 18 19 20 p u b l i c c l a s s XSLT { s t a t i c p u b l i c S t r i n g t r a n s f o r m ( F i l e x s l t , F i l e doc ) { r e t u r n t r a n s f o r m ( new StreamSource ( doc ) , new StreamSource ( doc ) ) ; } 21 s t a t i c p u b l i c S t r i n g t r a n s f o r m ( S o u r c e x s l t , S o u r c e doc ) { try { S t r i n g W r i t e r w r i t e r = new S t r i n g W r i t e r ( ) ; Transformer t = TransformerFactory . newInstance ( ) . newTransformer ( x s l t ) ; t . t r a n s f o r m ( doc , new StreamR esult ( w r i t e r ) ) ; return writer . getBuffer () . toString () ; } c a t c h ( T r a n s f o r m e r E x c e p t i o n _) { return ”” ; } } 22 23 24 25 26 27 28 29 30 31 32 33 34 35 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n { System . out . p r i n t l n ( t r a n s f o r m ( new F i l e ( a r g s [ 0 ] ) , new F i l e ( a r g s [ 1 ] ) ) ) ; } 36 37 38 39 40 } 101 Kapitel 2 Hierarchische Strukturen Listing 2.76: XSLT 2.5.4 Das SAX-API Oft brauchen wir nie das komplette XML-Dokument als Baum im Speicher. Eine Großzahl der Anwendungen auf XML-Dokumenten geht einmal das Dokument durch, um irgendwelche Informationen darin zu finden, oder ein Ergebnis zu erzeugen. Hierzu reicht es aus, immer nur einen kleinen Teil des Dokuments zu betrachten. Und tatsächlich hätte diese Vorgehensweise, bai allen bisher geschriebenen Programmen gereicht. Wir sind nie im Baum hin und her gegangen. Wir sind nie von einem Knoten zu seinem Elternknoten oder seinen vor ihm liegenden Geschwistern gegangen. Ausgehend von dieser Beobachtuung hat eine Gruppe von Programmierern ein API zur Bearbeitungen von XML-Dokumenten vorgeschlagen, das nie das gesammte Dokument im Speicher zu halten braucht. Dieses API heißt SAX, für simple api for xml processing. SAX ist keine Empfehlung des W3C. Es ist außerhalb des W3C entstanden. Die Idee von SAX ist ungefähr die, daß uns jemand das Dokument vorliest, einmal von Anfang bis Ende. Wir können dann auf das gehörte reagieren. Hierzu ist für einen Parse mit einem SAX-Parser stets mit anzugeben, wie auf das Vorgelesene reagiert werden soll. Dieses ist ein Objekt der Klasse DefaultHandler. In einem solchen handler sind Methoden auszuprogrammieren, in denen spezifiziert ist, was gemacht werden soll, wenn ein Elementstarttag, Elementendtag, Textknoten etc. vorgelsen wird. Man spricht bei einem SAX-Parser von einem ereignisbasierten Parser. Wir reagieren auf bestimmte Ereignisse des Parses, nämlich dem Starten/Enden von Elementen und so weiter. Auch ein SAX-Parser liegt in Java nur als Schnittstelle vor und kann nur über eine statische Fabrikmethode instanziiert werden. 1 2 3 4 5 package name . p a n i t z . xml . sax ; import o r g . xml . sax . h e l p e r s . D e f a u l t H a n d l e r ; import j a v a x . xml . p a r s e r s . * ; import o r g . xml . sax . * ; import j a v a . i o . F i l e ; 6 7 8 9 10 11 12 13 p u b l i c c l a s s SaxParse { public s t a t i c void parse ( F i l e f i l e , DefaultHandler handler ) throws E x c e p t i o n { SAXParserFactory . n e w I n s t a n c e ( ) . newSAXParser ( ) . parse ( f i l e , handler ) ; } 102 2.5 XML Verarbeitung 14 } Listing 2.77: SaxParse.java Als erstes Beispiel wollen wir unser altbekanntes Zählen der Knoten programmieren. Hierzu ist ein eigener DefaultHandler zu schreiben, der, sobald beim Vorlesen ihm der Beginn eines Elements gemeldet wird, darauf reagiert, indem er seinen Zähler um eins weiterzählt. Wir überschreiben demnach genau eine Methode aus dem DefaultHandler, nämlich die Methode startElement. 1 package name . p a n i t z . xml . sax ; 2 3 4 5 import o r g . xml . sax . A t t r i b u t e s ; import o r g . xml . sax . SAXException ; import o r g . xml . sax . h e l p e r s . D e f a u l t H a n d l e r ; 6 7 8 p u b l i c c l a s s Count e x t e n d s D e f a u l t H a n d l e r { int result = 0; 9 @Override p u b l i c v o i d s t a r t E l e m e n t ( S t r i n g u r i , S t r i n g localName , S t r i n g qName , A t t r i b u t e s a t t r i b u t e s ) throws SAXException { r e s u l t ++; } 10 11 12 13 14 15 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n { Count c o u n t e r = new Count ( ) ; SaxParse . p a r s e ( new j a v a . i o . F i l e ( a r g s [ 0 ] ) , c o u n t e r ) ; System . out . p r i n t l n ( c o u n t e r . r e s u l t ) ; } 16 17 18 19 20 21 } Listing 2.78: Count.java 1 package name . p a n i t z . xml . sax ; 2 3 4 import j a v a . u t i l . S e t ; import j a v a . u t i l . T r e e S e t ; 5 6 7 8 import o r g . xml . sax . A t t r i b u t e s ; import o r g . xml . sax . SAXException ; import o r g . xml . sax . h e l p e r s . D e f a u l t H a n d l e r ; 9 10 11 p u b l i c c l a s s GetTagNames e x t e n d s D e f a u l t H a n d l e r { Set<S t r i n g > r e s u l t = new TreeSet <>() ; 12 13 14 15 16 @Override p u b l i c v o i d s t a r t E l e m e n t ( S t r i n g u r i , S t r i n g localName , S t r i n g qName , A t t r i b u t e s a t t r i b u t e s ) throws SAXException { r e s u l t . add (qName) ; 103 Kapitel 2 Hierarchische Strukturen } 17 18 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n { GetTagNames t a g C o l l e c t o r = new GetTagNames ( ) ; SaxParse . p a r s e ( new j a v a . i o . F i l e ( a r g s [ 0 ] ) , t a g C o l l e c t o r ) ; System . out . p r i n t l n ( t a g C o l l e c t o r . r e s u l t ) ; } 19 20 21 22 23 24 25 } Listing 2.79: GetTagNames.java 1 package name . p a n i t z . xml . sax ; 2 3 4 5 import o r g . xml . sax . A t t r i b u t e s ; import o r g . xml . sax . SAXException ; import o r g . xml . sax . h e l p e r s . D e f a u l t H a n d l e r ; 6 7 8 9 p u b l i c c l a s s GetProgramCode e x t e n d s D e f a u l t H a n d l e r { S t r i n g B u f f e r r e s u l t = new S t r i n g B u f f e r ( ) ; b o o l e a n isInCodeElement = f a l s e ; 10 @Override p u b l i c v o i d s t a r t E l e m e n t ( S t r i n g u r i , S t r i n g localName , S t r i n g qName , A t t r i b u t e s a t t r i b u t e s ) throws SAXException { i f (qName . e q u a l s ( ” code ” ) ) { isInCodeElement = t r u e ; } } 11 12 13 14 15 16 17 18 @Override p u b l i c v o i d c h a r a c t e r s ( c h a r [ ] ch , i n t s t a r t , i n t l e n g t h ) throws SAXException { S t r i n g t e x t = new S t r i n g ( ch , s t a r t , l e n g t h ) ; i f ( isInCodeElement ) r e s u l t . append ( t e x t ) ; } 19 20 21 22 23 24 25 26 @Override p u b l i c v o i d endElement ( S t r i n g u r i , S t r i n g localName , S t r i n g qName) throws SAXException { i f (qName . e q u a l s ( ” code ” ) ) { isInCodeElement = f a l s e ; } } 27 28 29 30 31 32 33 34 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n { GetProgramCode c o d e E x t r a c t o r = new GetProgramCode ( ) ; SaxParse . p a r s e ( new j a v a . i o . F i l e ( a r g s [ 0 ] ) , c o d e E x t r a c t o r ) ; System . out . p r i n t l n ( c o d e E x t r a c t o r . r e s u l t ) ; } 35 36 37 38 39 40 } 104 2.5 XML Verarbeitung Listing 2.80: GetProgramCode.java 2.5.5 Das StAx-API Wir haben bisher drei methodisch sehr unterschiedliche Herangehensweisen für die Programmierung von XML Dokumenten kennen gelernt: • das DOM-API als ein Baum-API. • das SAX-API als ein Ereignis-basiertes API. • XPath als spezielle Programmiersprache, zur Beschreibung der Selektion von Knoten aus der Baumstruktur des Dokuments. Als Abschluss betrachten wir noch eine vierte methodische Herangehensweise. Dabei schließen wir wieder den Kreis zum ersten Kapitel, nämlich zu den Iteratoren. Ein weiteres API zur XML Verarbeitung in Java ist das strombasierte API StAX. Dahinter verbirgt sich wie bei SAX, die Verarbeitung des Dokuments sequentiell in seiner textuellen Form. Damit steht wieder nicht die reine Baumstruktur direkt im Speicher zur Verfügung. Die sequentielle Verarbeitung wird durch einen Iterator über die verschiedenen XML-Ereignisse bewerkstelligt. In Java finden sich die entsprechenden Definitionen als Schnittstellen im Paket javax.xml.stream. Die entscheidenen beiden Schnittstellen sind dabei: • XMLEventReader: diese Schnittstelle beschreibt einen Iterator der beim Lesen eines XML-Dokuments über die einzelnen XML-Komponenten des Dokuments iteriert. Leider berücksichtigt diese Schnittstelle nicht die generischen Typen von Java, so dass die Methode next() als Rückgabetyp lediglich Object hat. • javax.xml.stream.events.XMLEvent: diese Schnittstelle beschreibt allgemein ein XML-Konstrukt, auf dem beim Lesen gestoßen wird. Für die verschiedenen XML-Anteile gibt es Unterschnittstellen dieser Schnittstelle wie z.B. StartElement, EndElement oder Characters. In der Schnittstelle XMLEvent sind boolsche Methoden definiert, die testen, ob ein Objekt von einer dieser Unterschnittstellen ist. Es gibt z.B. die Methode boolean isAttribute();. Die Anwendung des StAX-APIs geht sehr analog, zu der Anwendung der anderen beiden XML APIs. 1 package name . p a n i t z . xml . s t a x ; 2 3 4 5 import j a v a . i o . FileNotFoundException ; import j a v a . i o . F i l e R e a d e r ; import j a v a . u t i l . I t e r a t o r ; 6 7 import j a v a x . xml . stream . F a c t o r y C o n f i g u r a t i o n E r r o r ; 105 Kapitel 2 Hierarchische Strukturen 8 9 10 11 import import import import j a v a x . xml . stream . XMLEventReader ; j a v a x . xml . stream . XMLInputFactory ; j a v a x . xml . stream . XMLStreamException ; j a v a x . xml . stream . e v e n t s . XMLEvent ; 12 13 p u b l i c c l a s s StaxTest { 14 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws FileNotFoundException , XMLStreamException , FactoryConfigurationError { 15 16 17 18 Listing 2.81: StaxTest.java Ein StAX basierter Parser wird durch eine Factory instanziiert: f i n a l XMLEventReader s t a x R e a d e r = XMLInputFactory . newInstance ( ) . createXMLEventReader ( new F i l e R e a d e r ( a r g s [ 0 ] ) ) ; 19 20 21 22 23 24 i n t r e s u l t =0; 25 Listing 2.82: StaxTest.java Das so erhaltene XMLEventReader Objekt kann zum Iterieren durch das Dokument benutzt werden. Leider kennt das StAX-API nicht die Schnittstelle Iterable, so dass nicht direkt die for-each Schleife zum Iterieren angewendet werden kann. Wir können uns da mit der Erzeugung eines iterierbaren Objektes durch einen LambdaAusdruck behelfen: I t e r a b l e <XMLEvent> i t = ( ) −> s t a x R e a d e r ; f o r (XMLEvent ev : i t ) { 26 27 Listing 2.83: StaxTest.java Für dieses Beispiel wollen wir die Anzahl der Elementknoten zählen. Ist das iterierte Objekt der Anfang eines Elements, also ein Start-Tag, dann zählen wir die Ergebnisvariable um eins hoch. i f ( ev . i s S t a r t E l e m e n t ( ) ) r e s u l t ++; } System . out . p r i n t l n ( r e s u l t ) ; 28 29 30 } 31 32 } Listing 2.84: StaxTest.java 106 Kapitel 3 Eine graphische Komponente für Baumstrukturen Zum Abschluss dieses Kapitels über Baumstrukturen, sollen die Bäume auch in einer graphischen Komponente angezeigt werden können. Fast jede GUI-Bibliothek hat eine Komponente zur Darstellung von hierarchischen Strukturen, um zum Beispiel das Dateisystem anzeigen zu können. Im Swing-API ist dieses die Klasse JTree. Die Klasse JTree hat einen Konstruktor, dem man einen Baumknoten übergeben kann. Hierzu kennt das API eine Schnittstelle TreeNode, die allgemein die verlangten Eigenschaften eines Baumknotens in sieben Methoden beschreibt. Allerdings kann man in der regel nicht davon ausgehen, dass eine Baumstruktur so entworfen wurde, dass sie diese Schnittstelle implementiert. Die Schnittstelle Node des DOM-APIs kennt die Schnittstelle TreeNode aus Swing genauso wenig, wie unsere Klasse Tree. Deshalb gibt es eine zweite, flexiblere Möglichkeit, um der graphischen Komponente JTree die Baumdaten zu übergeben. Hierzu definiert Swing die Schnittstelle TreeModel. 1 2 3 4 package name . p a n i t z . u t i l ; import j a v a x . swing . e v e n t . T r e e M o d e l L i s t e n e r ; import j a v a x . swing . t r e e . TreeModel ; import j a v a x . swing . t r e e . TreePath ; 5 6 7 8 9 10 p u b l i c c l a s s MyTreeModel implements TreeModel { Tree<?> t r e e ; p u b l i c MyTreeModel ( Tree<?> t r e e ) { this . tree = tree ; } 11 12 13 14 15 @Override p u b l i c Object getRoot ( ) { return tree ; } 16 17 18 @Override p u b l i c Object g e t C h i l d ( Object parent , i n t i n d e x ) { 107 Kapitel 3 Eine graphische Komponente für Baumstrukturen Tree<?> t = ( Tree <?>) p a r e n t ; return t . childNodes . get ( index ) ; 19 20 } 21 22 @Override p u b l i c i n t getChildCount ( Object p a r e n t ) { Tree<?> t = ( Tree <?>) p a r e n t ; return t . childNodes . s i z e () ; } 23 24 25 26 27 28 @Override p u b l i c b o o l e a n i s L e a f ( Object node ) { Tree<?> t = ( Tree <?>)node ; r e t u r n t . c h i l d N o d e s . isEmpty ( ) ; } 29 30 31 32 33 34 @Override p u b l i c v o i d valueForPathChanged ( TreePath path , Object newValue ) { throw new Unsu pp ortedOperati onExc eptio n ( ) ; } 35 36 37 38 39 @Override p u b l i c i n t g e t I n d e x O f C h i l d ( Object parent , Object c h i l d ) { Tree<?> t = ( Tree <?>) p a r e n t ; int i = 0; f o r ( Tree<?> c : t . c h i l d N o d e s ) { i f ( c==c h i l d ) r e t u r n i ; i ++; } r e t u r n −1; } 40 41 42 43 44 45 46 47 48 49 50 @Override p u b l i c void addTreeModelListener ( TreeModelListener l ) { 51 52 } 53 @Override p u b l i c void removeTreeModelListener ( TreeModelListener l ) { 54 55 56 } } Listing 3.1: MyTreeModel.java Bei genauer Betrachtung dieser Umsetzung stellt man fest, dass die Schnittstelle TreeModel leider kein Gebrauch von generischen Typen macht. Daher sind wir gezwungen mit Typzusicherungen zu arbeiten. Wir können aber eine abstrakte Klasse vorsehen, die Gebrauch von generischen Typen macht, und die TreeModel implementiert. Die Klasse bekommt einen Typparameter für den Typ der Baumknoten. Die entsprechenden Typzusicherungen werden dann in der abstrakten Klasse durchgeführt. Zwei abstrakte Methoden bekommen dann eine Signatur, die direkt Parameter der Baumknoten erhält. 108 1 package name . p a n i t z . u t i l ; 2 3 4 5 import j a v a x . swing . e v e n t . T r e e M o d e l L i s t e n e r ; import j a v a x . swing . t r e e . TreeModel ; import j a v a x . swing . t r e e . TreePath ; 6 7 8 p u b l i c a b s t r a c t c l a s s Ab st r a c t T re e M o de l l <T> implements TreeModel { T tree ; 9 10 11 12 13 p u b l i c A b s t r a c t T r e e M o d e l l (T t r e e ) { super () ; this . tree = tree ; } 14 15 16 17 18 @Override p u b l i c T getRoot ( ) { return tree ; } 19 20 21 22 23 24 @Override p u b l i c T g e t C h i l d ( Object parent , i n t i n d e x ) { T t = (T) p a r e n t ; return getChildAtIndex ( t , index ) ; } 25 26 a b s t r a c t p u b l i c T g e t C h i l d A t I n d e x (T parent , i n t i n d e x ) ; 27 28 29 30 31 32 33 @Override p u b l i c i n t getChildCount ( Object p a r e n t ) { T t = (T) p a r e n t ; return getChildSize ( t ) ; } 34 35 a b s t r a c t p u b l i c i n t g e t C h i l d S i z e (T t ) ; 36 37 38 39 40 @Override p u b l i c b o o l e a n i s L e a f ( Object node ) { r e t u r n getChildCount ( node ) ==0; } 41 42 43 44 45 @Override p u b l i c v o i d valueForPathChanged ( TreePath path , Object newValue ) { throw new UnsupportedOp erationExcep ti on ( ) ; } 46 47 48 49 50 51 52 @Override p u b l i c i n t g e t I n d e x O f C h i l d ( Object parent , Object c h i l d ) { T t = (T) p a r e n t ; T c = (T) c h i l d ; return getChildIndex ( t , c ) ; } 109 Kapitel 3 Eine graphische Komponente für Baumstrukturen 53 p u b l i c i n t g e t C h i l d I n d e x (T t , T c ) { f o r ( i n t i =0; i <getChildCount ( t ) ; i ++){ i f ( c == g e t C h i l d ( t , i ) ) r e t u r n i ; } r e t u r n −1; } 54 55 56 57 58 59 60 61 @Override p u b l i c v o i d a d d T r e e M o d e l L i s t e n e r ( T r e e M o d e l L i s t e n e r l ) {} 62 63 64 @Override p u b l i c void removeTreeModelListener ( TreeModelListener l ) { } 65 66 67 } Listing 3.2: AbstractTreeModell.java Um jetzt für eine Baumklasse ein Modell zu bekommen, reicht es aus, von der abstrakten Klasse abzuleiten und zwei naheliegende Methoden zu implementieren. Eine Methode, um die Anzahl der Kinder zu erfragen, eine um ein bestimmtes Kind zu erhalten. 1 2 3 4 package name . p a n i t z . u t i l ; import j a v a x . swing . JTree ; import j a v a x . swing . e v e n t . T r e e S e l e c t i o n E v e n t ; import j a v a x . swing . e v e n t . T r e e S e l e c t i o n L i s t e n e r ; 5 6 p u b l i c c l a s s TreeTreeModel e x t e n d s A b st ra c t T r e e M o d e l l <Tree<?>>{ 7 8 9 10 p u b l i c TreeTreeModel ( Tree<?> t r e e ) { super ( t r e e ) ; } 11 12 13 14 @Override p u b l i c Tree<?> g e t C h i l d A t I n d e x ( Tree<?> parent , i n t i n d e x ) { return parent . childNodes . get ( index ) ; } 15 16 17 18 @Override p u b l i c i n t g e t C h i l d S i z e ( Tree<?> p a r e n t ) { return parent . childNodes . s i z e () ; } 19 20 21 22 23 24 25 26 27 28 p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { j a v a x . swing . JFrame f = new j a v a x . swing . JFrame ( ) ; JTree j t = new JTree ( new TreeTreeModel ( Tree . c1 ) ) ; j t . a d d T r e e S e l e c t i o n L i s t e n e r ( new T r e e S e l e c t i o n L i s t e n e r ( ) { @Override p u b l i c v o i d valueChanged ( T r e e S e l e c t i o n E v e n t e ) { Object n = e . getPath ( ) . getLastPathComponent ( ) ; System . out . p r i n t l n ( n ) ; } 110 }) ; f . add ( j t ) ; f . pack ( ) ; f . s e t V i s i b l e ( true ) ; 29 30 31 32 } 33 34 } Listing 3.3: TreeTreeModel.java Aufgabe 1 (Nur für Studiengang WI) Schreiben sie eine Swing GUI-Komponente DOMTreeView, die eine Unterklasse von JTree ist und die Elementknoten eines XML-DOM Baumes darstellen kann. Schreiben Sie hierzu eine Klasse DomTreeModel, die Unterklasse der Klasse AbstractTreeModel ist. Aufgabe 1 (Projektblatt für Studiengang WI) a) Schreiben Sie eine Swing GUI-Komponente XMLViewer, die von JSplitPane ableitet. Auf der linken Seite des Split-Pane soll mit einem DOMTreeView ein XML-Dokument in seiner Elementstruktur dargestellt werden. Klickt man auf einen der Elementknoten, soll in einer JTextArea auf der rechten Seite der Textinhalt des Elementknotens dargestellt werden. b) Schreiben Sie eine Swing GUI Applikation XMLWorkbench. Hier soll es möglich sein eine XML Datei zu laden. Es soll ein XPath-Ausdruck eingegeben werden, die Ergebnisse der XPath-Auswertung sollen als XML in einer XMLViewer Komponente angezeigt werden können. Die Ergebnisse der XPath Berechnung sollen auch abgespeichert werden können. Zur Auswertung des XPath-Audruckes sollen dabei die Klassen des Standardpakets javax.xml.xpath verwendet werden. Sehen Sie in der GUI-Applikation auch eine TextArea vor, in der Log Information angezeigt werden kann, z.B. wenn Exceptions aufgetreten sind. c) Erweitern Sie die XMLWorkbench um die Möglichkeit, dass ein XSL-Skript in einer JTextArea geladen und auch editiert werden kann. Schließlich soll es möglich sein, das XSL-Skript auf das geladene XML-Dokument anzuwenden, das Ergebnis anzuzeigen und abzuspeichern. 111 </div> </div> </div> <!-- End Description Section --> </main> <!-- ========== END MAIN ========== --> <div id="embedModal" class="js-login-window u-modal-window u-modal-window--embed"> <button class="btn btn-xs u-btn--icon u-btn-text-secondary u-modal-window__close" type="button" onclick="Custombox.modal.close();"> <span class="fas fa-times"></span> </button> <form class="p-7"> <header class="text-center mb-7"> <h4 class="h4 mb-0">Embed!</h4> <p>Programmiermethoden - Hochschule Rheinmain</p> </header> <textarea class="form-control u-form__input" rows="5"></textarea> </form> </div> <script> function check_recatpcha(token) { document.getElementById("download-form").submit(); grecaptcha.reset(); } </script> <script src='https://www.google.com/recaptcha/api.js'></script> <!-- ========== FOOTER ========== --> <hr class="my-0"> <footer> <!-- Lists --> <div class="container u-space-2"> <div class="row justify-content-md-between"> <div class="col-sm-4 col-lg-2 mb-4 mb-lg-0"> <h3 class="h6"> <strong>About us'</strong> </h3> <!-- List --> <ul class="list-unstyled mb-0"> <li><a class="u-list__link" href="https://pdfkiwi.com/about-us">About us</a> </li> <li><a class="u-list__link" href="https://pdfkiwi.com/terms-conditions">Terms and conditions</a> </li> <li><a class="u-list__link" href="https://pdfkiwi.com/privacy-policy">Privacy policy</a></li> <li><a class="u-list__link" href="https://pdfkiwi.com/sitemap">Sitemap</a></li> <li><a class="u-list__link" href="https://pdfkiwi.com/career">Career</a> </li> <li><a class="u-list__link" href="https://pdfkiwi.com/contact-us">Contact us</a></li> </ul> <!-- End List --> </div> <div class="col-sm-4 col-lg-2 mb-4 mb-lg-0"> <h3 class="h6"> <strong>Support</strong> </h3> <!-- List --> <ul class="list-unstyled mb-0"> <li><a class="u-list__link" href="https://pdfkiwi.com/help">Help</a></li> <li><a class="u-list__link" href="https://pdfkiwi.com/ticket">Submit ticket</a></li> </ul> <!-- End List --> </div> <div class="col-sm-4 col-lg-2 mb-4 mb-lg-0"> <h3 class="h6"> <strong>Account</strong> </h3> <!-- List --> <ul class="list-unstyled mb-0"> <li><a class="u-list__link" href="https://pdfkiwi.com/profile">Profile</a> </li> <li><a class="u-list__link" href="https://pdfkiwi.com/login">Login</a> </li> <li><a class="u-list__link" href="https://pdfkiwi.com/register">Register</a> </li> <li><a class="u-list__link" href="https://pdfkiwi.com/recover-account">Forgot password</a> </li> </ul> <!-- End List --> </div> <div class="col-md-6 col-lg-4"> <h3 class="h6"> <strong>Connect with us</strong> </h3> <!-- Social Networks --> <ul class="list-inline mb-0"> <li class="list-inline-item mb-3"> <a class="u-icon u-icon--sm u-icon-primary--air rounded" href="https://facebook.com/pdfkiwicom"> <span class="fab fa-facebook-f u-icon__inner"></span> </a> </li> <li class="list-inline-item mb-3"> <a class="u-icon u-icon--sm u-icon-primary--air rounded" href="https://plus.google.com/111647055250435329124"> <span class="fab fa-google u-icon__inner"></span> </a> </li> <li class="list-inline-item mb-3"> <a class="u-icon u-icon--sm u-icon-primary--air rounded" href="https://twitter.com/pdfkiwicom"> <span class="fab fa-twitter u-icon__inner"></span> </a> </li> </ul> <!-- End Social Networks --> </div> </div> </div> <!-- End Lists --> <hr> <!-- Copyright --> <div class="container text-center u-space-1"> <!-- Logo --> <a class="d-inline-block mb-2" href="https://pdfkiwi.com/" aria-label="PDFKIWI"> <img src="https://pdfkiwi.com/assets/img/logo.png" alt="Logo" style="width: 120px;"> </a> <!-- End Logo --> <p class="small text-muted">Copyright © 2012-2024.</p> </div> <!-- End Copyright --> </footer> <!-- ========== END FOOTER ========== --> <!-- ========== SECONDARY CONTENTS ========== --> <!-- Account Sidebar Navigation --> <aside id="sidebarContent" class="u-sidebar u-unfold--css-animation u-unfold--hidden" aria-labelledby="sidebarNavToggler"> <div class="u-sidebar__scroller"> <div class="u-sidebar__container"> <div class="u-header-sidebar__footer-offset"> <!-- Toggle Button --> <div class="d-flex align-items-center pt-4 px-7"> <button type="button" class="close ml-auto" aria-controls="sidebarContent" aria-haspopup="true" aria-expanded="false" data-unfold-event="click" data-unfold-hide-on-scroll="false" data-unfold-target="#sidebarContent" data-unfold-type="css-animation" data-unfold-animation-in="fadeInRight" data-unfold-animation-out="fadeOutRight" data-unfold-duration="500"> <span aria-hidden="true">×</span> </button> </div> <!-- End Toggle Button --> <!-- Content --> <div class="js-scrollbar u-sidebar__body"> <div class="u-sidebar__content u-header-sidebar__content"> <!-- Login --> <div id="login" data-target-group="idForm"> <form class="js-validate" action="https://pdfkiwi.com/login" method="post"> <!-- Title --> <header class="text-center mb-7"> <h2 class="h4 mb-0">Welcome back</h2> <p>Login to manage your account</p> </header> <!-- End Title --> <!-- Input --> <div class="js-form-message mb-4"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fa fa-user u-form__text-inner"></span> </span> </div> <input type="email" class="form-control u-form__input" name="email" required placeholder="Email address" aria-label="Email address" data-msg="Please enter a valid email address" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <!-- Input --> <div class="js-form-message mb-2"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fa fa-lock u-form__text-inner"></span> </span> </div> <input type="password" class="form-control u-form__input" name="password" required placeholder="Password" aria-label="Password" data-msg="Your password is invalid please try again" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <div class="clearfix mb-4"> <a class="js-animation-link float-right small u-link-muted" href="javascript:;" data-target="#forgotPassword" data-link-group="idForm" data-animation-in="slideInUp">Forgot password</a> </div> <div class="mb-2"> <button type="submit" class="btn btn-block btn-primary u-btn-primary transition-3d-hover">Login </button> </div> <div class="text-center mb-4"> <span class="small text-muted">Do not have an account?</span> <a class="js-animation-link small" href="javascript:;" data-target="#signup" data-link-group="idForm" data-animation-in="slideInUp">Register </a> </div> <div class="text-center"> <span class="u-divider u-divider--xs u-divider--text mb-4">Or</span> </div> <!-- Login Buttons --> <div class="d-flex"> <a class="btn btn-block btn-sm u-btn-facebook--air transition-3d-hover mr-1" href="https://pdfkiwi.com/login/facebook"> <span class="fab fa-facebook-square mr-1"></span> Facebook </a> <a class="btn btn-block btn-sm u-btn-google--air transition-3d-hover ml-1 mt-0" href="https://pdfkiwi.com/login/google"> <span class="fab fa-google mr-1"></span> Google </a> </div> <!-- End Login Buttons --> </form> </div> <!-- Signup --> <div id="signup" style="display: none; opacity: 0;" data-target-group="idForm"> <form class="js-validate" action="https://pdfkiwi.com/register" method="post"> <!-- Title --> <header class="text-center mb-7"> <h2 class="h4 mb-0">Welcome to PDFKIWI.</h2> <p>Fill out the form to get started</p> </header> <!-- End Title --> <!-- Input --> <div class="js-form-message mb-4"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fa fa-user u-form__text-inner"></span> </span> </div> <input type="email" class="form-control u-form__input" name="email" required placeholder="Email address" aria-label="Email address" data-msg="Please enter a valid email address" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <!-- Input --> <div class="js-form-message mb-4"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fa fa-user u-form__text-inner"></span> </span> </div> <input type="text" class="form-control u-form__input" name="username" required placeholder="Username" aria-label="Username" data-msg="Please enter a valid username" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <!-- Input --> <div class="js-form-message mb-4"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fa fa-lock u-form__text-inner"></span> </span> </div> <input type="password" class="form-control u-form__input" name="password" required placeholder="Password" aria-label="Password" data-msg="Your password is invalid please try again" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <!-- Input --> <div class="js-form-message mb-4"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fa fa-key u-form__text-inner"></span> </span> </div> <input type="password" class="form-control u-form__input" name="confirm_password" id="confirmPassword" required placeholder="Confirm password" aria-label="Confirm password" data-msg="Password does not match with confirm password" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <!-- Checkbox --> <div class="js-form-message mb-5"> <div class="custom-control custom-checkbox d-flex align-items-center text-muted"> <input type="checkbox" class="custom-control-input" id="termsCheckbox" name="terms_confirm" value="1" required data-msg="Please accept our terms and conditions" data-error-class="u-has-error" data-success-class="u-has-success"> <label class="custom-control-label" for="termsCheckbox"> <small> I agree to the <a class="u-link-muted" href="https://pdfkiwi.com/terms-conditions">Terms and conditions</a> </small> </label> </div> </div> <!-- End Checkbox --> <div class="mb-2"> <button type="submit" class="btn btn-block btn-primary u-btn-primary transition-3d-hover">Get started </button> </div> <div class="text-center mb-4"> <span class="small text-muted">Already have account?</span> <a class="js-animation-link small" href="javascript:;" data-target="#login" data-link-group="idForm" data-animation-in="slideInUp">Login </a> </div> <div class="text-center"> <span class="u-divider u-divider--xs u-divider--text mb-4">Or</span> </div> <!-- Login Buttons --> <div class="d-flex"> <a class="btn btn-block btn-sm u-btn-facebook--air transition-3d-hover mr-1" href="#"> <span class="fab fa-facebook-square mr-1"></span> Facebook </a> <a class="btn btn-block btn-sm u-btn-google--air transition-3d-hover ml-1 mt-0" href="#"> <span class="fab fa-google mr-1"></span> Google </a> </div> <!-- End Login Buttons --> </form> </div> <!-- End Signup --> <!-- Forgot Password --> <div id="forgotPassword" style="display: none; opacity: 0;" data-target-group="idForm"> <form class="js-validate" action="https://pdfkiwi.com/recover-account" method="post"> <!-- Title --> <header class="text-center mb-7"> <h2 class="h4 mb-0">Forgot your password?.</h2> <p>Enter your email address below and we will get you back on track</p> </header> <!-- End Title --> <!-- Input --> <div class="js-form-message mb-4"> <div class="js-focus-state input-group u-form"> <div class="input-group-prepend u-form__prepend"> <span class="input-group-text u-form__text"> <span class="fas fa-envelope u-inner-form__text"></span> </span> </div> <input type="email" class="form-control u-form__input" name="email" required placeholder="Email address" aria-label="Email address" data-msg="Please enter a valid email address" data-error-class="u-has-error" data-success-class="u-has-success"> </div> </div> <!-- End Input --> <div class="mb-2"> <button type="submit" class="btn btn-block btn-primary u-btn-primary transition-3d-hover">Request reset link </button> </div> <div class="text-center mb-4"> <span class="small text-muted">Remember your password?</span> <a class="js-animation-link small" href="javascript:;" data-target="#login" data-link-group="idForm" data-animation-in="slideInUp">Login </a> </div> </form> </div> <!-- End Forgot Password --> </div> </div> <!-- End Content --> </div> <!-- Footer --> <footer class="u-sidebar__footer u-sidebar__footer--account"> <ul class="list-inline mb-0"> <li class="list-inline-item pr-3"> <a class="u-sidebar__footer--account__text" href="https://pdfkiwi.com/terms-conditions">Terms and conditions</a> </li> <li class="list-inline-item"> <a class="u-sidebar__footer--account__text" href="https://pdfkiwi.com/help"> <i class="fa fa-info-circle"></i> Help </a> </li> </ul> <!-- SVG Background Shape --> <div class="position-absolute-bottom-0"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 300 126.5" style="margin-bottom: -5px; enable-background:new 0 0 300 126.5;" xml:space="preserve"> <path class="u-fill-primary" opacity=".6" d="M0,58.9c0-0.9,5.1-2,5.8-2.2c6-0.8,11.8,2.2,17.2,4.6c4.5,2.1,8.6,5.3,13.3,7.1C48.2,73.3,61,73.8,73,69 c43-16.9,40-7.9,84-2.2c44,5.7,83-31.5,143-10.1v69.8H0C0,126.5,0,59,0,58.9z"/> <path class="u-fill-primary" d="M300,68.5v58H0v-58c0,0,43-16.7,82,5.6c12.4,7.1,26.5,9.6,40.2,5.9c7.5-2.1,14.5-6.1,20.9-11 c6.2-4.7,12-10.4,18.8-13.8c7.3-3.8,15.6-5.2,23.6-5.2c16.1,0.1,30.7,8.2,45,16.1c13.4,7.4,28.1,12.2,43.3,11.2 C282.5,76.7,292.7,74.4,300,68.5z"/> <circle class="u-fill-danger" cx="259.5" cy="17" r="13"/> <circle class="u-fill-primary" cx="290" cy="35.5" r="8.5"/> <circle class="u-fill-success" cx="288" cy="5.5" r="5.5"/> <circle class="u-fill-warning" cx="232.5" cy="34" r="2"/> </svg> </div> <!-- End SVG Background Shape --> </footer> <!-- End Footer --> </div> </div> </aside> <!-- End Account Sidebar Navigation --> <!-- ========== END SECONDARY CONTENTS ========== --> <!-- Go to Top --> <a class="js-go-to u-go-to" href="#" data-position='{"bottom": 15, "right": 15 }' data-type="fixed" data-offset-top="400" data-compensation="#header" data-show-effect="slideInUp" data-hide-effect="slideOutDown"> <span class="fa fa-arrow-up u-go-to__inner"></span> </a> <!-- End Go to Top --> <!-- JS Global Compulsory --> <script src="https://pdfkiwi.com/assets/vendor/jquery/dist/jquery.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/jquery-migrate/dist/jquery-migrate.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/popper.js/dist/umd/popper.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/bootstrap/bootstrap.min.js"></script> <!-- JS Implementing Plugins --> <script src="https://pdfkiwi.com/assets/vendor/hs-megamenu/src/hs.megamenu.js"></script> <script src="https://pdfkiwi.com/assets/vendor/malihu-custom-scrollbar-plugin/jquery.mCustomScrollbar.concat.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/jquery-validation/dist/jquery.validate.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/fancybox/jquery.fancybox.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/typed.js/lib/typed.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/slick-carousel/slick/slick.js"></script> <script src="https://pdfkiwi.com/assets/vendor/pdfobject/pdfobject.js"></script> <script src="https://pdfkiwi.com/assets/vendor/custombox/dist/custombox.min.js"></script> <script src="https://pdfkiwi.com/assets/vendor/appear.js/appear.js"></script> <script src="https://pdfkiwi.com/assets/vendor/dzsparallaxer/dzsparallaxer.js"></script> <script src="https://pdfkiwi.com/assets/vendor/cubeportfolio/js/jquery.cubeportfolio.min.js"></script> <!-- JS Template --> <script src="https://pdfkiwi.com/assets/js/hs.core.js"></script> <script src="https://pdfkiwi.com/assets/js/helpers/hs.focus-state.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.header.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.unfold.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.malihu-scrollbar.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.validation.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.fancybox.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.slick-carousel.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.show-animation.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.sticky-block.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.scroll-nav.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.go-to.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.modal-window.js"></script> <script src="https://pdfkiwi.com/assets/js/components/hs.cubeportfolio.js"></script> <script src="https://pdfkiwi.com/assets/js/pdfkiwi.js?v=2"></script> <script> // initialization of text animation (typing) if (jQuery('.u-text-animation.u-text-animation--typing').length > 0) { var typed = new Typed(".u-text-animation.u-text-animation--typing", { strings: ["Documents.", "Magazines.", "Articles.", "And more."], typeSpeed: 60, loop: true, backSpeed: 25, backDelay: 1500 }); } </script> </body> </html>