Informatik C Vorlesungsskript WS 2002/2003 Jürgen Schönwälder Version vom 30. Januar 2003 Fachbereich Mathematik/Informatik Universität Osnabrück Vorwort Die Vorlesung Informatik C vermittelt einen Einblick in die ingenieurmäßige Konstruktion von komplexen Software-Systemen (Software-Engineering) und vertieft die Java-Programmierkenntnisse zur Realisierung von graphischen Benutzungsoberflächen. Die Vorlesung setzt Kenntnisse aus den Vorlesungen Informatik A (Algorithmen) und Informatik B (objekt-orientierte Programmierung mit Java) voraus und vertieft einige Themen aus diesen Vorlesungen. Einige Beispiele im ersten Kapitel stammen aus einer Vorlesung mit dem Titel Fehlertolerante ” Systeme“, die ich an der TU Braunschweig angeboten habe. Einige Details im Kapitel über Prozessmodelle und im Kapitel zum Thema Qualitätssicherung sind den Folienskripten zur Vorlesung Einführung in Software Engineering“ an der TU Braunschweig von Prof. Dr. G. Snelting, Prof. Dr. ” A. Zeller, Dr. M. Huhn und Dr. A. Zündorf entnommen. Bedanken möchte ich mich an dieser Stelle auch bei allen Studenten, die durch kritische Fragen dazu beigetragen haben, dass dieses Skript etwas weniger Fehler enthält und hoffentlich besser verständlich geworden ist. Besonderer Dank auch an Elmar Ludwig, der die Übungen im WS 2001/2002 und im WS 2002/2003 betreut und viel konstruktive Kritik an diesem Skript geübt hat. Bedanken möchte ich mich an dieser Stelle auch bei Astrid Heinze für das gelegentliche Korrekturlesen einzelner Kapitel während der Überarbeitung des Skripts im WS 2002/2003. Jürgen Schönwälder Inhaltsverzeichnis I Software Engineering 1 Einführung 1.1 Softwarekrise . . . . . . . . 1.2 Fehlertolerante Software . . 1.3 Sicherheitskritische Software 1.4 Software-Qualität . . . . . . 2 3 1 . . . . . . . . Prozessmodelle 2.1 Code-and-Fix . . . . . . . . . . 2.2 Wasserfallmodell . . . . . . . . 2.2.1 Planungsphase . . . . . 2.2.2 Definitionsphase . . . . 2.2.3 Entwurfsphase . . . . . 2.2.4 Implementierungsphase 2.2.5 Installationsphase . . . . 2.2.6 Wartungsphase . . . . . 2.3 V-Modell . . . . . . . . . . . . 2.4 Prototyping . . . . . . . . . . . 2.5 Spiral-Modell . . . . . . . . . . 2.6 Transformationelles Modell . . . 2.7 Extremes Programmieren (XP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unified Modeling Language (UML) 3.1 Notizen . . . . . . . . . . . . . . . . . . 3.2 Klassendiagramme . . . . . . . . . . . . 3.2.1 Klassen . . . . . . . . . . . . . . 3.2.2 Sichtbarkeit . . . . . . . . . . . . 3.2.3 Gültigkeitsbereiche . . . . . . . . 3.2.4 Generalisierung / Spezialisierung 3.2.5 Abstraktheit . . . . . . . . . . . . 3.2.6 Aggregation / Komposition . . . . 3.2.7 Assoziationen . . . . . . . . . . . 3.2.8 Schnittstellen und Realisierungen 3.2.9 Abhängigkeiten . . . . . . . . . . 3.2.10 Objekte . . . . . . . . . . . . . . 3.2.11 Pakete . . . . . . . . . . . . . . . 3.3 Sequenzdiagramme . . . . . . . . . . . . 3.4 Kollaborationsdiagramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 4 6 7 8 . . . . . . . . . . . . . 13 15 16 17 17 18 18 18 19 19 20 20 21 21 . . . . . . . . . . . . . . . 25 25 25 26 27 27 28 29 29 30 32 33 33 35 37 39 INHALTSVERZEICHNIS 3.5 3.6 3.7 3.8 3.9 3.10 4 5 6 Zustandsdiagramme . . . . Aktivitätsdiagramme . . . Anwendungsfalldiagramme Komponentendiagramme . Verteilungsdiagramme . . Beispiel: Fahrstuhl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Entwurfsmuster 4.1 Factory Method . . . . . . . . . . . 4.2 Iterator . . . . . . . . . . . . . . . . 4.3 Composite . . . . . . . . . . . . . . 4.4 Observer . . . . . . . . . . . . . . . 4.5 Strategy . . . . . . . . . . . . . . . 4.6 Chain of Responsibility . . . . . . . 4.7 Model View Controller . . . . . . . 4.8 Anti Pattern: Creeping Featuritis . . 4.9 Anti Pattern: Design By Committee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 41 43 44 45 46 . . . . . . . . . 51 54 55 58 61 65 66 67 68 68 Qualitätssicherung 5.1 Typische Softwarefehler . . . . . . . . . . . 5.2 Programminspektionen . . . . . . . . . . . . 5.3 Testverfahren . . . . . . . . . . . . . . . . . 5.3.1 Funktionale Testverfahren . . . . . . 5.3.2 Kontrollfluss-orientierte Testverfahren 5.3.3 Mutationstesten . . . . . . . . . . . . 5.3.4 Regressionstesten . . . . . . . . . . . 5.4 Softwaremetriken . . . . . . . . . . . . . . . 5.5 Qualitätssicherung mit ISO 9000 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 69 69 70 71 73 75 76 76 78 Werkzeuge 6.1 Debugger . . . . . . . . . . . . . . . . . . 6.1.1 Java Debugger (jdb) . . . . . . . . 6.1.2 Data Display Debugger (ddd) . . . 6.2 Programmkonstruktion . . . . . . . . . . . 6.2.1 make . . . . . . . . . . . . . . . . 6.3 Versionsmanagement . . . . . . . . . . . . 6.3.1 Revision Control System (RCS) . . 6.3.2 Concurrent Versions System (CVS) 6.4 Testwerkzeuge . . . . . . . . . . . . . . . 6.4.1 JUnit . . . . . . . . . . . . . . . . 6.5 Integrierte Entwicklungsumgebungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 79 79 80 80 80 82 82 83 84 84 87 . . . . . . . . . . . II Graphische Benutzungsoberflächen 89 7 91 91 92 92 Ergonomische Aspekte 7.1 Dialogarten und Dialogmodi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Grundsätze zur Dialoggestaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.1 Aufgabenangemessenheit . . . . . . . . . . . . . . . . . . . . . . . . . . INHALTSVERZEICHNIS . . . . . . . 92 93 93 93 94 94 94 8 Java Abstract Windowing Toolkit (AWT) 8.1 AWT Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2 Ereignisorientiere Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . 97 97 98 9 Java Foundation Classes (Swing) 9.1 Einführende Beispiele . . . . . . . . . . . . . . . 9.1.1 Hello World . . . . . . . . . . . . . . . . 9.1.2 Celsius Converter . . . . . . . . . . . . . 9.1.3 Look and Feel . . . . . . . . . . . . . . 9.1.4 Root Panes . . . . . . . . . . . . . . . . 9.1.5 Threads . . . . . . . . . . . . . . . . . . 9.2 Layout Manager . . . . . . . . . . . . . . . . . . 9.2.1 Border Layout . . . . . . . . . . . . . . 9.2.2 Box Layout . . . . . . . . . . . . . . . . 9.2.3 Card Layout . . . . . . . . . . . . . . . 9.2.4 Flow Layout . . . . . . . . . . . . . . . 9.2.5 Grid Layout . . . . . . . . . . . . . . . . 9.2.6 Grid Bag Layout . . . . . . . . . . . . . 9.2.7 Absolute Positionierung . . . . . . . . . 9.2.8 Implementation eines Layout-Manager . 9.3 Ränder . . . . . . . . . . . . . . . . . . . . . . . 9.3.1 Einfache Ränder . . . . . . . . . . . . . 9.3.2 Ränder mit Titeln . . . . . . . . . . . . . 9.3.3 Zusammengesetzte Ränder . . . . . . . . 9.4 Menüs . . . . . . . . . . . . . . . . . . . . . . . 9.4.1 Toolbars . . . . . . . . . . . . . . . . . . 9.4.2 Actions . . . . . . . . . . . . . . . . . . 9.5 Scrolling . . . . . . . . . . . . . . . . . . . . . . 9.6 Splitting . . . . . . . . . . . . . . . . . . . . . . 9.7 Text Komponenten . . . . . . . . . . . . . . . . 9.7.1 Dokumente . . . . . . . . . . . . . . . . 9.7.2 Ereignisse von Dokumenten . . . . . . . 9.7.3 Dokument Editor . . . . . . . . . . . . . 9.7.4 Edieren von Dokumenten . . . . . . . . 9.8 Dateidialoge . . . . . . . . . . . . . . . . . . . . 9.9 Bäume . . . . . . . . . . . . . . . . . . . . . . . 9.10 Tabellen . . . . . . . . . . . . . . . . . . . . . . 9.11 Zweidimensionale Graphik . . . . . . . . . . . . 9.11.1 Primitive geometrische Formen (Shapes) 9.11.2 Beispiel: Animation springender Bälle . . 7.3 7.2.2 Selbstbeschreibungsfähigkeit . . . . . 7.2.3 Steuerbarkeit . . . . . . . . . . . . . 7.2.4 Erwartungskonformität . . . . . . . . 7.2.5 Fehlertoleranz . . . . . . . . . . . . 7.2.6 Individualisierbarkeit . . . . . . . . . 7.2.7 Lernförderlichkeit . . . . . . . . . . Acht goldene Regeln für die Dialoggestaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 106 106 107 111 114 114 115 115 116 119 122 123 124 126 127 132 132 134 136 138 141 143 147 149 151 151 152 153 156 163 167 170 174 176 182 INHALTSVERZEICHNIS 9.11.3 Beispiel: Santa Claus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 III Software-Komponenten und Verteilte Anwendungen 195 10 Java Beans 10.1 Properties . . . . . . . . . . . . . 10.2 Explizite Beschreibung einer Bean 10.3 Verpacken einer Bean . . . . . . . 10.4 Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 197 204 206 207 11 Servlets 11.1 Grundlagen der Web-Technologie 11.1.1 URIs, URLs und URNs . 11.1.2 MIME . . . . . . . . . . . 11.1.3 HTTP . . . . . . . . . . . 11.1.4 HTML . . . . . . . . . . 11.2 Generische Servlets . . . . . . . . 11.3 HTTP Servlets . . . . . . . . . . 11.4 Lebenszyklus und Container . . . 11.5 Sessions . . . . . . . . . . . . . . 11.6 JavaServer Pages (JSP) . . . . . . 11.6.1 Deklarationen . . . . . . . 11.6.2 Ausdrücke . . . . . . . . 11.6.3 Scriptlets . . . . . . . . . 11.6.4 Einfügungen . . . . . . . 11.6.5 Weiterleitungen . . . . . . 11.6.6 Seiten-Direktiven . . . . . 11.7 JavaServer Pages und Beans . . . 11.7.1 Bean-Benutzung . . . . . 11.7.2 Properties . . . . . . . . . 11.8 Beispiel: Raten von Nummern . . 11.9 Tomcat Referenzimplementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 211 211 211 211 212 212 214 216 216 217 219 219 219 220 220 220 221 221 222 222 224 12 Extensible Markup Language (XML) 12.1 Struktur von XML Dokumenten . 12.2 Document Type Definitions (DTD) 12.3 XML Namensräume . . . . . . . 12.4 XML Schema (XSD) . . . . . . . 12.5 XML Paths (XPATH) . . . . . . . 12.6 XML Stylesheet Language (XSL) 12.7 Document Object Model (DOM) . 12.8 Java DOM API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 225 227 228 228 231 231 232 232 13 Verteilte Anwendungen 13.1 Client/Server-Modell . . . 13.2 N-Tier Architekturen . . . 13.2.1 2-Tier Architektur 13.2.2 3-Tier Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 233 234 235 235 . . . . . . . . . . . . . . . . INHALTSVERZEICHNIS 13.2.3 4-Tier Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 14 CORBA 14.1 CORBA Grundlagen . . . . . . . . 14.2 Object Request Broker (ORBs) . . . 14.3 Object Services . . . . . . . . . . . 14.4 Common Facilities . . . . . . . . . 14.5 Komponenten eines ORBs . . . . . 14.6 Interface Definition Language (IDL) 14.6.1 Typsystem . . . . . . . . . 14.6.2 Konstanten . . . . . . . . . 14.6.3 Ausnahmen . . . . . . . . . 14.6.4 Interfaces . . . . . . . . . . 14.6.5 Module . . . . . . . . . . . 14.7 Objekt-Referenzen . . . . . . . . . 14.8 Java Language Mapping . . . . . . 14.9 Naming Service . . . . . . . . . . . 14.10Mau Mau . . . . . . . . . . . . . . 14.11Schlußbemerkungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Remote Method Invocation (RMI) 237 237 238 238 238 238 239 240 241 241 241 242 242 243 246 248 264 265 16 Java Name and Directory Interface (JNDI) 269 16.1 JNDI-Terminologie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 16.2 Zugriff auf Namensdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 16.3 Zugriff auf Verzeichnisdienste . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 17 Enterprise Java Beans 277 17.1 Eigenschaften von EJBs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 17.2 Eigenschaften eines Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 17.3 Aufbau einer EJB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 18 Java Database Connectivity (JDBC) 18.1 Relationale Datenbanken . . . . . . . 18.2 Structured Query Language . . . . . . 18.2.1 Data Definition Language . . 18.2.2 Data Manipulation Language . 18.2.3 Data Control Language . . . . 18.3 SQL Zugriffe aus Java mit JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 281 282 282 283 284 285 Teil I Software Engineering 1 Kapitel 1 Einführung Eigenschaften von Software [1]: • Software ist ein immaterielles Produkt. Software kann man nicht anfassen oder sehen. Entsprechend schwierig ist es beispielsweise festzustellen, ob die Dokumentation mit dem lauffähigen Programmcode übereinstimmt. • Software unterliegt keinem Verschleiß. Software nutzt sich nicht ab und es ist daher eine klassische Wartung (Austausch verschlissener Teile) nicht notwendig. • Software wird nicht durch physikalische Gesetze begrenzt. Software basiert nicht auf physikalischen Gesetzen und erlaubt damit dem Schöpfer von Software einen großen Gestaltungsspielraum. • Software ist leicht und schnell änderbar. Gut strukturierte und modularisierte Software ermöglicht es, Änderungen einfach und schnell durchzuführen. • Software altert. Zunächst scheint die Aussage falsch zu sein, da Software keinem Verschleißprozess unterliegt. Berücksichtigt man jedoch, dass sich die Umgebung, in der eine Software eingesetzt wird, ständig ändert, dann wird diese Aussage verständlich. • Software ist schwer zu vermessen. Für Software gibt es keine exakten Messgeräte. Die Qualität von Software ist schwer definierbar und quantifizierbar. Definition des Begriffs Software-Engineering (Software-Technik): • Die zielorientierte Bereitstellung und die systematische Verwendung von Prinzipien, Methoden und Werkzeugen für die arbeitsteilige Entwicklung und Anwendung von umfangreichen Software-Systemen. Zielorientiert bedeutet Berücksichtigung z.B. von Kosten, Zeit, Qualität [1]. • Das ingenieurmäßige Entwerfen, Herstellen und Implementieren von Software sowie die ingenieurwissenschaftliche Disziplin, die sich mit Methoden und Verfahren zur Lösung der damit verbundenen Problemstellungen befasst. (Brockhaus-Enzyklopädie) 3 4 KAPITEL 1. EINFÜHRUNG • The systematic approach to the development, operation, maintenance, and requirement of software. (ANSI/IEEE Standard Glossary of Software Engineering Terminology, 1983) • Software engineering = multi-person construction of multi-version software. (Parnas 1987) • The application of a systematic, disciplined, quantifiable approach to the development, operation, and maintenance of software; that is, the application of engineering to software. (ANSI/IEEE Standard Glossary of Software Engineering Terminology, 1990) 1.1 Softwarekrise Ende der 60er Jahre wurden die ersten großen kommerziellen Softwaresysteme entwickelt und beobachtet, dass die vorhandenen Vorgehensweisen nicht erfolgreich auf die neuen größeren Projekte übertragen werden konnten. Viele Softwareprojekte scheiterten und es wurde der Begriff der Softwarekrise geprägt. Die aktuelle Lage ist nach Untersuchungen von Laprie (1999) immer noch nicht rosig: • Gut ein Drittel der Software-Projekte wird zur Zufriedenheit des Kunden und des Anbieters durchgeführt. • Bei einem Drittel der Software-Projekte wird ein Produkt oder eine Dienstleistung abgeliefert, die in wesentlichen Teilen den Anforderungen des Kunden nicht entspricht. • Knapp ein Drittel der Software-Projekte enden ohne Auslieferung der Leistung (also der Software) an den Kunden. Häufige Problemquellen: • Größe und Komplexität von Softwaresystemen SAP R/3 hatte 1994 ca. 7.000.000 Zeilen Quellcode (in der Hochsprache Abab bzw. C++), ca. 100.000 Funktionsaufrufe, ca. 20.000 Funktionen, ca. 14.000 Funktionsbausteine und ca. 17.000 Menüleisten. SAP hat allein am Hauptsitz in Walldorf mehrere tausend Entwickler beschäftigt. • Integration mit anderen (informations)technischen Systemen Fast alle Software-Systeme müssen heutzutage mit anderen (informations)technischen Systemen interagieren und Daten austauschen können (Business to Business, Business to Customer). • Steigende Qualitätsanforderungen Nach Cusumano (MIT Sloan School of Management) wurden pro 1000 Zeilen Quelltext 1977 durchschnittlich 7-20 Defekte gefunden. 1994 waren es noch durchschnittlich 0.05-0.2 Defekte. Die Defektrate konnte also um ungefähr das 100fache in 15 Jahren gesenkt werden. Allerdings ist ein Defektniveau von einem Defekt auf 1000 Zeilen Quelltext bei weitem noch nicht akzeptabel. Für die Personenbeförderung ist beispielsweise eine Fehlerrate < 10−7 des technischen Geräts notwendig. • Flexibilitätsanforderungen bei der Entwicklung 1.1. SOFTWAREKRISE 5 – Produktanforderungen und die umgebenden Komponenten verändern sich, insbesondere in den späteren Phasen des Software-Lebenszyklus. – Die eingesetzten Methoden und Werkzeuge verändern sich ebenfalls während der Entwicklung. • Portabilitätsanforderungen – Anwendungssoftware hat eine Lebensdauer von ca. 10-15 Jahren. In der Regel muss die Software ca. 1-2 Wechsel der darunterliegenden Systemsoftware sowie 3-4 Hardwarewechsel überstehen. – Durch den Trend zu Standardsoftware müssen viele Versionen für spezifische Kundenanforderungen erstellt und gewartet werden (Hardwareanpassungen, Sprachanpassungen der Benutzungsoberfläche, länder- und anwenderspezifische Vorschriften). • Organisation der Entwicklung und Wartung – Häufig scheitern Softwareprojekte nicht an technischen Problemen, sondern an schlechter Kommunikation (Benutzer - Entwickler, Entwickler - Entwickler), mangelhafter Ausbildung, hoher Personalfluktuation und mangelhaftem Projektmanagement. Prominente Probleme und Katastrophen: • Viele Programme des letzten Jahrhunderts speicherten Jahreszahlen als zweistellige Dezimalzahlen (73 für 1973), was bei der Umstellung auf das Jahr 2000 verstärkt zu Problemen führen kann (00-73 = -73 anstatt 2000-1973 = 27). Mit einem enormen Aufwand mussten alte Programme getestet und überarbeitet werden. • Häufige Ausfälle bei Büro-Standardsoftware marktführender Hersteller. • Der Verlust des Mars-Climate-Orbiters im September 1999 ist auf mangelnde Kompatibilität zurückzuführen. Die NASA hatte zwei Firmen mit der Implementation von SoftwareKomponenten für die Navigation beauftragt. Die eine berechnete die physikalischen Daten in metrischen Maßeinheiten, während die andere mit englischen Maßeinheiten rechnete. Die Verwendung unterschiedlicher Maßeinheiten war bekannt und wurde an fast allen Stellen berücksichtigt. Erst beim Anflug der Marsatmosphäre führte eine Interaktion dieser Komponenten mit Daten in der falschen Maßeinheit dazu, dass der Orbiter zu nah an den Mars geriet und verglühte. • Die amerikanische Viking Venus Sonde wurde durch eine fehlerhaft programmierte DOSchleife (Fortran) verloren. Anstatt des korrekten Quelltextes DO 20 I = 1,100 enthielt das Programm die folgende Zeile: DO 20 I = 1.100 Als Folge wurde DO20I als eine implizite Variable interpretiert und eine Zuweisung durchgeführt anstatt eine Schleife zu durchlaufen (Fortran erlaubt Leerzeichen in Bezeichnern). • Viele weitere Beispiele findet man unter http://catless.ncl.ac.uk/Risks/. 6 KAPITEL 1. EINFÜHRUNG 1.2 Fehlertolerante Software Fehlertolerante Software versucht auch in bestimmten möglichen Fehlersituationen weiterhin korrekte Ergebnisse zu liefern. Fehlertoleranz ist immer dann erforderlich, wenn der Ausfall eines Software-Systems katastrophale Folgen haben kann: • Verlust von Aktienwerten durch fehlerhafte oder nicht vorhandene Informationen (materieller Schaden) • Ausfall eines Flugüberwachungssystems (materielle Schäden und ggf. Verluste von menschlichem Leben) • Fehlfunktion oder Ausfall einer Prozesssteuerungsanlage (materielle Schäden und ggf. ökologische und gesundheitliche Schäden) • Fehlfunktionen in elektronischen Stellsystemen für den spurgeführten Verkehr (materielle Schäden und evtl. Gefährdung menschlichen Lebens) • Fehlfunktion oder Ausfall eines medizinischen Hilfsgeräts (Personenschäden und evtl. Verlust von menschlichem Leben) Ausfälle entstehen durch Fehler im System oder von außen kommende Störungen. Prinzipiell gilt: • Je mehr Komponenten ein System besitzt, desto mehr können fehlerhaft sein. • Je komplexer eine Komponente ist, umso größer ist die Chance, dass die Komponente fehlerhaft ist. Beispiel Telefon-Vermittlungssysteme: • Typische Anforderungen an Telefon-Vermittlungssysteme (AT&T, USA): – Weniger als 1 Stunde ungeplante Ausfallzeit in 20 Jahren bei fortlaufendem Betrieb. (Das entspricht wenige als 3 Minuten Ausfallzeit pro Jahr.) – Maximal eine von 10.000 Telefonverbindungen darf durch einen Fehler unterbrochen werden. – Die Zeit vom Abnehmen des Hörers bis zum Freizeichen darf maximal 1 Sekunde betragen. – Bei der Durchführung von Wartungsarbeiten (z.B. Erneuerung von Software und Hardware) dürfen keine bereits begonnenen Telefongespräche gestört werden. – Die benutzte Software, die System-Konfiguration und die Datenbanken müssen ohne Unterbrechung des Betriebs gepflegt werden können. • Ein Ausfall des AT&T Backbone-Netzes von 9 Stunden Dauer hat im Januar 1990 zu einem Schaden von 60-75 Millionen US $ geführt. • Nach einer US Studie verlieren große Firmen bei Ausfällen ihrer lokalen Netzinfrastruktur mehr als 30.000 US $ pro Stunde. 1.3. SICHERHEITSKRITISCHE SOFTWARE 7 1.3 Sicherheitskritische Software Beispiel Eisenbahnen: • Klassische Eisenbahnen funktionieren nach dem failsafe-Prinzip, bei dem alle möglichen Ausfälle nur in einen sicheren Zustand führen dürfen. • Die klassische Signaltechnik nutzt unverlierbare physikalische Eigenschaften (wie z.B. die Schwerkraft oder Magnetismus), um Signale failsafe zu realisieren. • Elektronische Stellwerke werden mit spezieller redundanter Rechnerhardware aufgebaut, die ebenfalls beim Ausfall nachweisbar die Einnahme eines sicheren Zustands garantiert. • Die Programmierung elektronischer Stellwerke erfolgt mit aufwendigen Prüfungen (Inspektionen des Quelltextes, Abnahmeprüfungen) und mit speziellen geprüften Entwicklungswerkzeugen. Beispiel Flugzeuge: • Flugzeuge können im Gegensatz zu Eisenbahnen bei Ausfällen nicht einfach in einen sicheren Zustand gebracht werden. • Flugunfälle haben meist schwerwiegende Folgen, oftmals mit Verlusten von menschlichem Leben. • Zur Sicherung wird Redundanz eingesetzt, wobei maximal die Wahrscheinlichkeit eines katastrophalen Ausfalls hinreichend klein gemacht werden kann, aber grundsätzlich keine Katastrophen ausgeschlossen werden können. Subjektive Sicherheitsbewertung durch den Menschen: • Der Mensch beurteilt die Sicherheit eines technischen Systems subjektiv und nicht objektiv. • Ein seltener spektakulärer Unfall findet mehr Beachtung als häufige unspektakuläre Unfälle. • Nach einem spektakulären Unfall finden eine gewisse Zeit auch unspektakuläre Unfälle Beachtung, die sonst nicht beachtet würden. • Die Möglichkeit der direkten Einflussnahme lässt Systeme sicherer erscheinen. • Die zu erwartenden persönlichen Vorteile durch die Benutzung eines Systems beeinflussen die Risikobereitschaft. Ansätze zu einer objektiven Sicherheitsbewertung: • Mit Hilfe von Wahrscheinlichkeitsmodellen wird die Schadenswahrscheinlichkeit eines technischen Systems ermittelt. • Das allgemeine Lebensrisiko ist die Wahrscheinlichkeit einer Person, innerhalb einer Stunde das Leben zu verlieren. 8 KAPITEL 1. EINFÜHRUNG • Es wird allgemein angenommen, dass eine Absenkung der Schadenswahrscheinlichkeit unter das allgemeine Lebensrisiko (10−7 ) nicht sinnvoll ist. −6 Lebensrisiko 10 −7 10 Alter 10 20 30 40 50 60 Abbildung 1.1: Lebensrisiko im Bevölkerungsdurchschnitt (1950-1952) 1.4 Software-Qualität Software-Qualität ist nach DIN ISO 9126 die Gesamtheit der Merkmale und Eigenschaften eines Software-Produkts, die sich auf dessen Eignung beziehen, festgelegte oder vorausgesetzte Erfordernisse zu erfüllen. Software-Qualität wird typischerweise aus zwei Sichten beurteilt: 1. Anwendersicht: Aspekte des Systems, die für den Anwender wichtig und sichtbar sind wie Funktionsumfang, Effizienz, Benutzbarkeit oder Sicherheit. 2. Entwicklersicht: Aspekte des Systems, die für den Entwicklungsprozess, den Einsatz und die Pflege relevant sind wie Testbarkeit, Wartbarkeit, Erweiterbarkeit oder Wiederverwendbarkeit. Beispiel: Kunden beurteilen die Qualität von Benutzungsoberflächen nach den Möglichkeiten sich verschiedenen Benutzerklassen (Anfänger, Experte) anzupassen. Entwickler beurteilen die Qualität nach einer gelungenen Entkoppelung der Oberfläche von der Anwendungslogik. Anmerkungen: • Es ist die Verantwortung des Entwicklers, die Qualität aus Entwicklersicht zu gewährleisten. • Die Qualitätsanforderungen aus Anwendersicht müssen erfüllt werden, auch wenn sie dem Entwickler weniger wichtig erscheinen. • Software-Qualität kann nur vor dem Hintergrund der Kosten und des Zeitrahmens eines Entwicklungsprozesses beurteilt werden. Qualitätsmerkmale nach DIN ISO 9126: 1.4. SOFTWARE-QUALITÄT 9 • Funktionalität: Vorhandensein von Funktionen mit festgelegten Eigenschaften. Diese Funktionen erfüllen die definierten Anforderungen. • Zuverlässigkeit: Fähigkeit der Software, ihr Leistungsniveau unter festgelegten Bedingungen über einen festgelegten Zeitraum zu bewahren. • Benutzbarkeit: Aufwand, der zur Benutzung erforderlich ist, und individuelle Beurteilung der Benutzung durch eine festgelegte vorausgesetzte Benutzergruppe. • Effizienz: Verhältnis zwischen dem Leistungsniveau der Software und dem Umfang der eingesetzten Betriebsmittel unter festgelegten Bedingungen. Es ist oft relativ einfach die Effizienz eines gut strukturierten Programms nachträglich zu verbessern. Andererseits ist es in der Regel extrem aufwendig, ein zwar effizientes aber unstrukturiertes Programm zu erweitern. Bei Maßnahmen zur Effizienzverbesserung ist es sinnvoll strukturiert vorzugehen, da das intuitive Verständnis vom dynamischen Programmverhalten oftmals falsch ist. Vorgehensweise zur Effizienzverbesserung (tuning): 1. Kritische Teile des Programms identifizieren (Messungen, Simulationsmodelle, analytische Verfahren) 2. Gezielte Optimierung der kritischen Stellen (bessere Algorithmen, hardwarenahe Realisierung kritischer Teile, Verteilung der Funktionen auf mehrere Prozessoren) 3. Einsatz schnellerer Hardware (Vorsicht vor wachsenden Grundlasten) • Änderbarkeit: Aufwand, der zur Durchführung vorgegebener Änderungen notwendig ist. Änderungen können Korrekturen, Verbesserungen oder Anpassungen an Änderungen der Umgebung, der Anforderungen und der funktionalen Spezifikation sein. • Übertragbarkeit: Eignung der Software, von einer Umgebung in eine andere übertragen zu werden. Umgebung kann organisatorische Umgebung, Hardware- und Software-Umgebung einschließen. Weitere Qualitätsmerkmale: • Robustheit: Software ist robust, wenn der potentielle Schaden, der durch Gebrauch, Fehlbedienung oder Missbrauch ausgelöst werden kann, gering ist. Beispiel: Ein Editor, der auf die Eingabe unzulässiger Befehle mit der Zerstörung der bearbeiteten Datei reagiert, ist in unzumutbarer Weise nicht robust. Viele Programme reagieren auf unerwartete Eingaben unkontrolliert [14]. Um die Robustheit zu erhöhen, wird oft das Verhalten in zu erwartenden Fehlersituationen spezifiziert und damit wird Robustheit ein Teil der Korrektheit. • Wartbarkeit: Bei wartbarer Software können Korrekturen, Systemanpassungen und Verbesserungen ohne großen Aufwand durchgeführt werden. Nach etlichen Studien machen Wartungskosten 60-80% der Softwarekosten aus. Davon sind ca. 20% Beseitigung von Fehlern, ca. 20% Adaptionen wie die Anpassung an neue Betriebssystemversionen und ca. 50% Vervollkommnung des Produkts (wie z.B. die Umstellung auf eine graphische Benutzungsoberfläche oder die Internationalisierung). 10 KAPITEL 1. EINFÜHRUNG Wartbarkeit wird unterstützt durch eine modulare Systemstruktur mit überschaubaren Schnittstellen, gute Dokumentation und wohldefinierte Änderungsprozeduren (Versions- und Variantenkontrolle). In einigen Fällen ist eine Möglichkeit zur Wartung der Software während des Betriebs erforderlich. • Erweiterbarkeit: Bei erweiterbarer Software wird der Aufwand zur Erweiterung des Funktionsumfangs im Wesentlichen vom Umfang der Erweiterung bestimmt. • Korrektheit: Korrekte Software erfüllt ihre Aufgabe gemäß der Spezifikation. – Ohne Spezifikation kann Korrektheit nicht gezeigt werden. – Nur eine hinreichend genaue Spezifikation erlaubt eine Korrektheitsprüfung. – Eine Validation ist eine Überprüfung des Systems durch Tests. – Eine Verifikation ist ein mathematischer Beweis, dass ein System eine formale Spezifikation erfüllt. – Korrekte Software garantiert keine zufriedenen Kunden. – Fehler und Auslassungen in der Spezifikation können ganze Softwareprojekte scheitern lassen. • Kompatibilität und Interoperabilität: Die Software kooperiert mit anderen technischen Systemen und Programmen in der vorgesehenen Art und Weise. Benutzer möchten in der Regel bisherige Daten weiterverwenden und Aufgaben, die sie mit den vorhandenen Systemen zufriedenstellend erledigen konnten, auch weiterhin in der gewohnten Weise durchführen. Kompatibilität wird verbessert durch offene Systeme und Standards: – wohldefinierte Schnittstellen (z.B. Dateiformate) – normierte Protokolle und Architekturen (TCP/IP, CORBA) – eigenständige Module mit festen Schnittstellen, die der Benutzer kombinieren kann • Sicherheit: Sichere Software wahrt die Integrität der Daten, schützt sie vor unberechtigtem Zugriff und erfüllt insbesondere anwendungsspezifische und gesetzliche Bestimmungen. Der deutsche Begriff Sicherheit umfasst die Begriffe Informationssicherheit (Security) und Funktionssicherheit (Safety): – Informationssicherheit ist der Schutz der Vertraulichkeit und die Integrität von Daten und Vorgängen. – Funktionssicherheit ist die Eigenschaft eines Systems, dass die realisierte Funktion der Komponenten mit der Spezifikation übereinstimmt und das System keine funktional unzulässigen Zustände annimmt. • Ergonomie: Software-Ergonomie befasst sich mit der Gestaltung interaktiver Programmsysteme und entwickelt Kriterien und Methoden, mit denen Programmsysteme den menschlichen Bedürfnissen entgegenkommend gestaltet werden können. – Angemessene Schnittstellen für verschiedene Anwenderklassen. – Oberflächenelemente sollten in jedem Fenster einer Anwendung einheitlich angeordnet und beschriftet sein. 1.4. SOFTWARE-QUALITÄT 11 – Vernünftige Reaktionen auf fehlerhaftes Benutzerverhalten (brauchbare Fehlermeldungen, undo/redo-Funktionen). • Erlernbarkeit: Die Eigenschaften eines Softwaresystems, die das Erlernen des Umgangs mit einer Software erleichtern. – Style-Guides für graphische Benutzungsoberflächen führen zu einer Vereinheitlichung der verschiedenen Anwendungen. – Einhalten von Quasi-Standards und insbesondere von Betriebssystemmechanismen. – Lern- und Hilfsprogramme – Gute Dokumentation • Wiederverwendbarkeit: Wiederverwendbare Software kann für neue Anwendungen mit wenig Aufwand angepasst werden. – Ziel ist ein Baukastensystem, bei dem Software aus Standardkomponenten zusammengesetzt wird. – Beispiele für erfolgreiche Wiederverwendung: Programmbibliotheken, Fenstersysteme, Datenbanken – Wiederverwendung wird hauptsächlich durch Abstraktion erreicht und erfordert einen Entwicklungsprozess auf hohem Abstraktionsniveau. Literaturhinweise Von H. Balzert stammt ein gutes und sehr umfassendes Lehrbuch zum Thema Software-Engineering [1]. Unbedingt lesenswert sind auch die Bücher von F. Brooks [5] und T. DeMarco [7]. Das Buch von F. Brooks dokumentiert Erfahrungen, die 1975 bei der Entwicklung eines neuen Betriebssystems für IBM Mainframes gemacht wurden und auch heute noch in vielen Projekten beobachtet werden können. Das Buch von T. DeMarco [7] beschreibt sehr unterhaltsam in einer Art Roman wie viele Software-Projekte in der Realität tatsächlich ablaufen. 12 KAPITEL 1. EINFÜHRUNG Kapitel 2 Prozessmodelle Um eine Softwareentwicklung nach festen Regeln nachvollziehbar und systematisch durchführen zu können, braucht man so genannte Prozessmodelle, die einen bestimmten organisatorischen Rahmen zur Entwicklung und Pflege von Software festlegen (Vorgehensmodell). Prozessmodelle legen insbesondere fest, welche Aktivitäten in welcher Reihenfolge von welchen Personen erledigt werden und welche Ergebnisse dabei entstehen und wie diese in der Qualitätssicherung überprüft werden. • Im Prozessmodell definierte Aktivitäten werden durch Personen ausgeführt, die definierte Rollen einnehmen. • Die Ergebnisse werden auch als Artefakte (artifacts) bezeichnet. (Ein Artefakt ist ein durch menschliches Können geschaffenes Kunsterzeugnis.) • Format und Struktur der Artefakte werden durch Muster bestimmt. • Ein Prozessmodell definiert Verantwortlichkeiten und Kompetenzen. • Ein Prozessmodell legt Richtlinien, Methoden und Werkzeuge fest, die während der Entwicklung beachtet/benutzt werden müssen. Das Software Engineering Institute (SEI) an der Carnegie Mellon University hat ein 5-StufenModell (capability maturity model, CMM) entwickelt, mit dem sich die tatsächlich erreichte Qualität des Entwicklungsprozesses beschreiben lässt: 1. Initial Level • Ad-hoc Vorgehensweise und Programmierung, wenig Formalisierung. • Werkzeuge und Methoden werden uneinheitlich eingesetzt. • Projektmanagement durch informelle Absprachen. ⇒ Kosten, Termine und Qualität sind unkontrolliert. 2. Repeatable Level • Vorgehen (Entwicklungsmethoden, Programmierstil) ist informell vereinheitlicht. • Einsatz von Werkzeugen zur Konfigurationsverwaltung und zum Terminmanagement. 13 14 KAPITEL 2. PROZESSMODELLE l ontro ess C Proc Optimized t emen anag ess M Proc ion efinit ss D roce (Level 5) Managed (Level 4) P Basic geme Mana trol t Con Defined n (Level 3) Repeatable (Level 2) Initial (Level 1) Abbildung 2.1: capability maturity model • Management und Hop- oder Topentscheidungen funktionieren. ⇒ Termine kontrolliert, Qualität und Kosten schwanken. 3. Defined Level • Vorgehensweise ist formalisiert, standardisiert und überprüft. • Klar definierte Prozessmodelle sind vorhanden. • Einsatz von Werkzeugen für Aufgaben des Prozessmanagements (Sammlung von Kenngrößen). • Einsatz von Werkzeugen, die den definierten Entwicklungsprozess unterstützen. ⇒ Termine und Kosten kontrolliert, Qualität schwankt. 4. Managed Level • Metriken zum Softwareentwicklungsprozess vorhanden. • Qualitätssicherung voll integriert. • Detaillierte Planung (sehr gute Schätzmethoden) und Kontrolle möglich. ⇒ Qualität, Termine und Kosten kontrolliert. 5. Optimized Level • Der Entwicklungsprozess selbst kann kontrolliert verbessert werden. Nach Studien des SEI an 3500 Softwareprojekten in den USA (Mai 1998) befinden sich 58% der Projekte im Initial Level, 24% im Repeatable Level, 12% im Defined Level, 2% im Managed Level und 1% im Optimized Level. Vergleiche mit Studien von 1991 zeigen, dass sich die Qualität der Entwicklungsprozesse deutlich verbessert hat. 15 2.1. CODE-AND-FIX 2.1 Code-and-Fix Das wohl einfachste Modell ist die Code-and-Fix Vorgehensweise, bei der Entwickler ohne ein festgelegtes Prozessmodell Funktionen implementieren. Anschließend werden die so entstandenen Komponenten ad-hoc getestet und gegebenenfalls verbessert. Dieses Modell ist sicherlich jedem Studenten aus eigenen Erfahrungen beim Lösen von Übungsaufgaben vertraut. Für kleine Projekte ist der Ansatz auch durchaus angemessen, er versagt aber total bei größeren Software-Projekten. Code schreiben Testen Verbessern Erweitern Abbildung 2.2: Code-and-Fix Vorgehensmodell Bewertung: + Geeignet für 1-Personen-Projekte im Umfang von Wochen, insbesondere wenn Entwickler und Anwender identisch sind. - Wartbarkeit sinkt kontinuierlich. - Fehlerwahrscheinlichkeit steigt. - Abhängigkeit vom Entwickler durch fehlende Dokumentation. - Tests und Verbesserungen sind häufig nicht zufriedenstellend. - Häufig erfüllt das Produkt nicht die Anforderungen des Kunden. ⇒ Größere Projekte können so nicht bewältigt werden. 16 KAPITEL 2. PROZESSMODELLE 2.2 Wasserfallmodell Das Wasserfallmodell ist ein früher grundlegender Ansatz zur Strukturierung des Entwicklungsprozesses. Es taucht in der Literatur in vielen verschiedenen Variationen auf. Der Name drückt aus, dass man sich wie bei einem mehrstufigen Wasserfall von der Planungsphase zur Wartungsphase bewegt. Planungsphase Durchf"uhrbarkeitsstudie Definitionsphase Pflichtenheft Entwurfsphase Produktentwurf Quelltext, Dokumentation, Implementierungs− Objektprogramm, Testprotokoll phase Installationsphase Abnahmeprotokoll Wartungsphase Abbildung 2.3: Wasserfallmodell • Grundlegendes Modell mit vielen Varianten. • Weitere Unterteilung der Phasen (z.B. Implementationsphase → Implementationsphase und Integrationsphase) möglich. • Jede Phase ist in der angegebenen Reihenfolge vollständig durchzuführen. • Jede Phase endet mit der Erstellung eines Dokuments. • Orientiert sich stark an der Top-Down-Vorgehensweise. • Möglichkeit zur Rückkehr zu früheren Phasen falls grundlegende Fehler oder Inkonsistenzen entdeckt werden. Bewertung: + Leicht verständliches Prozessmodell. + Trennung des Was“ vom Wie”. ” ” 2.2. WASSERFALLMODELL 17 - Kundenbeteiligung ist nur in der Definitionsphase vorgesehen. - Hohe Kosten, falls die Spezifikation nicht korrekt die Kundenwünsche wiedergibt. - Starres Modell: Oftmals ist es sinnvoller, Entwicklungsschritte nicht genau in der vorgegebenen Reihenfolge oder vorläufig nur partiell durchzuführen. - Konzentration auf die Entwicklung; Kriterien wie Änderungsfreundlichkeit sind zu wenig berücksichtigt. 2.2.1 Planungsphase Das Planen eines Softwareprodukts beinhaltet folgende Einzelaktivitäten: • Auswählen des Produkts durch Trendstudien, Marktanalysen, Forschungsergebnisse, Kundenanfragen, Vorentwicklungen. • Voruntersuchung des Produkts durch eine IST-Analyse und Festlegung von Hauptanforderungen und Leistungs- und Qualitätsmerkmalen. • Durchführbarkeitsuntersuchung (Realisierbarkeit, Alternativen, Wirtschaftlichkeitsprüfung, Prüfung der organisatorischen Rahmenbedingungen). Das Ergebnis ist eine Durchführbarkeitsstudie bestehend aus folgenden Artefakten: • Lastenheft (grobes Pflichtenheft) • Glossar (Begriffslexikon) • Projektkalkulation • Projektplan 2.2.2 Definitionsphase In der Definitionsphase werden die Anforderungen an die zu entwickelnde Software festgelegt. • Ermitteln der detaillierten Anforderungen. • Beschreibung und Analyse der Anforderungen. • Modellierung, Simulation, Animation. Das Ergebnis ist eine verbale Beschreibung der Anforderungen in Form eines Pflichtenhefts. - Das Pflichtenheft erklärt, was das Produkt mit welchen Qualitätsmerkmalen leisten soll (nicht wie). - Es sollte verständlich und eindeutig sein (für Kunde und Entwickler). - Es sollte konsistent und vollständig sein. 18 KAPITEL 2. PROZESSMODELLE 2.2.3 Entwurfsphase Aufgaben in der Entwurfsphase (Programmieren im Großen): • Festlegung der Softwarearchitektur (Zerlegung des Systems in Systemkomponenten). • Ermitteln und Festlegen der Umgebungs- und Randbedingungen. • Spezifikation der einzelnen Systemkomponenten. Das Ergebnis dieser Phase ist ein Produktentwurf, der aus einer Software-Architektur und den Spezifikationen der Systemkomponenten besteht. 2.2.4 Implementierungsphase Die Implementierung des Produkts (Programmieren im Kleinen) beinhaltet folgende Aktivitäten: • Konzeption der Datenstrukturen und Algorithmen. • Strukturierung des Programms durch geeignete Verfeinerungsebenen. • Dokumentation der Problemlösung und der Implementierungsentscheidungen. • Angaben zur Zeit- und Speicherkomplexität sowie Randbedingungen. • Umsetzung in eine Programmiersprache. • Test oder Verifikation des entwickelten Programms. Das Ergebnis der Implementierungsphase ist ein Quellprogramm (inklusive integrierter Dokumentation), ein Objektprogramm, die Testplanung und ein Testprotokoll. 2.2.5 Installationsphase In der Installationsphase wird das Softwareprodukt ausgeliefert. Das erfolgt häufig in zwei Stufen: • Zunächst erfolgt eine Auslieferung an ausgewählte Kunden, die Erfahrungen und Fehler zurückmelden. (Beta-Test) • Erst nach dieser Probezeit erfolgt die Auslieferung an alle Kunden. Bei der Auslieferung wird in der Regel ein Abnahmetest durchgeführt, der in einem Abnahmeprotokoll festgehalten wird. 19 2.3. V-MODELL 2.2.6 Wartungsphase Die in der Wartungsphase zu errichtenden Aktivitäten lassen sich in folgende Kategorien einteilen: • Stabilisierung / Korrektur • Optimierung / Leistungsverbesserung • Anpassung / Änderung • Erweiterung Der Aufwand für die Wartung ist in der Regel um den Faktor 2-4 größer als der Entwicklungsaufwand. Es lohnt sich also, bei der Entwicklung auf die Wartbarkeit zu achten. 2.3 V-Modell Das V-Modell ist eine Weiterentwicklung des Wasserfallmodells. Es drückt insbesondere die große Bedeutung der Validierung durch Tests aus. Das V-Modell ist bei der Softwareentwicklung für staatliche deutsche Organisationen und Behörden vorgeschrieben. Anwendungsszenarien Anforderungs− definition Abnahmetest Testfaelle Grobentwurf Systemtest Testfaelle Feinentwurf Integrationstest Modulimple− mentation Testfaelle Modultest Abbildung 2.4: V-Modell Bewertung: + Integration der Qualitätssicherung in das Wasserfallmodell 20 KAPITEL 2. PROZESSMODELLE + Gut geeignet für große Projekte - Bürokratie-Overhead für kleine und mittlere Projekte - Strikter Phasenablauf - Nicht methodenneutral und kaum Unterstützung durch Werkzeuge 2.4 Prototyping Beim Prototyping-Ansatz werden möglichst früh so genannte Prototypen (Vorführmodelle) für das zu entwickelnde System mit reduzierter Funktionalität oder Qualität entwickelt. • Ein Demonstrationsprototyp dient der Auftragsakquisation und wird meist mit Generatoren und Skript-Sprachen implementiert. • Ein horizontaler Prototyp realisiert nur spezifische Ebenen des Systems (z.B. die Benutzungsoberfläche), die aber möglichst vollständig. • Ein vertikaler Prototyp realisiert einen abgemagerten Funktionsumfang auf allen Ebenen. • Ein Pilotsystem ist ein Prototyp, der nicht nur zur experimentellen Erprobung oder zur Veranschaulichung dient, sondern selbst der Kern des Produkts ist. Ein Pilotsystem hilft, die organisatorische Integration des Produkts vorzubereiten, indem es dem Benutzer einen Vorgeschmack auf das System gibt. Bewertung: + Der Kunde erhält frühzeitig einen Prototypen zum experimentieren. + Erfahrungen und Änderungswünsche können frühzeitig integriert werden. - Große Gefahr, in das Code-and-Fix-Vorgehen zurückzufallen. - Große Versuchung, den Prototypen zum Endprodukt auszubauen. 2.5 Spiral-Modell Das Spiral-Modell ist ein evolutionäres Modell, bei dem zuerst Kernkomponenten identifiziert und realisiert werden. In weiteren Teilprojekten werden später weitere Komponenten hinzukommen. • Es ist notwendig, bei der Spezifikation und dem Entwurf von Kernkomponenten bereits Erweiterungen zu berücksichtigen. • Die Architektur muss für Erweiterungen offen gehalten werden. • Es ist jeweils zu prüfen, ob die Wiederverwendung von Komponenten oder der Einsatz von Standardsoftware möglich ist. Für jedes Teilprojekt und jede Verfeinerungsebene werden die folgenden Schritte durchlaufen: 2.6. TRANSFORMATIONELLES MODELL 21 1. Anforderungsdefinition 2. Bewertung von Alternativen und Risiken 3. Entwurf, Implementation und Test des Teilprodukts gemäß eines geeigneten Prozessmodells 4. Auswertung und Vorbereitung des nächsten Umlaufs Bewertung: + Risikominimierung in allen Phasen und bei allen Teilprojekten. + Jede Komponente kann nach dem geeignetsten Prozessmodell entwickelt werden. + Regelmäßige Bewertung und Korrektur der Ergebnisse. + Unterstützt und fördert Wiederverwendung. - Hoher Managementaufwand. - Bei vielen Iterationen geht die Phasentrennung verloren. 2.6 Transformationelles Modell Durch semantikerhaltende Transformationen wird eine abstrakte Spezifikation in ein ausführbares Programm überführt. Entwurfsentscheidungen gehen als Optimierungen ein. Bewertung: + Hoher Automatisierungsgrad + Ist die Spezifikation korrekt, so ist auch die Implementierung korrekt. - Bisher nur für spezielle Anwendungsgebiete praktisch einsetzbar (z.B. Codegenerierung aus Statecharts, Kommunikationsprotokolle) - Sehr anspruchsvoll und meist nur für kleine Projekte brauchbar. 2.7 Extremes Programmieren (XP) Als Alternative zu den relativ schwerfälligen Prozessmodellen wurde insbesondere für kleine Projekte Ende der neunziger Jahre Extremes Programmieren“ vorgeschlagen [2, 21]. Der Ansatz ist ” für kleine Projekte mit bis zu 10-15 Entwicklern geeignet und basiert auf den folgenden zwölf Techniken (Praktiken in der XP-Terminologie): 1. Kleine Releases • XP benutzt einen hochgradig iterativen Entwicklungsprozess mit einem Release-Zyklus von 1-3 Monaten. • Ein Release besteht aus mehreren Iterationen von 1-3 Wochen Dauer. 22 KAPITEL 2. PROZESSMODELLE • Iterationen zerfallen wiederum in Arbeitspakete von 1-3 Tagen. • Nach jeder Iteration kann der Kunde Abweichungen von seinen Vorstellungen feststellen und korrigieren. 2. Planungsspiel • Kunden beschreiben gewünschte neue Funktionen für die nächste Iteration in Form einer User Story“ und versehen sie mit Prioritäten. ” • Die Entwickler schätzen den Aufwand zur Realisierung für jede Story. • Der Kunde entscheidet, welche Story in der nächsten Iteration implementiert wird. • Gegebenenfalls ist eine Wiederholung des Planungsspiels notwendig, bis ein Gleichgewicht erreicht wird und eine realistische Planung für die nächste Iteration entstanden ist. 3. Tests • Automatisierte Tests spielen eine zentrale Rolle von XP. • Die Entwickler schreiben Tests für ihre Klassen (unit tests). • Der Kunde entwickelt Testfälle für eine Story (functional tests). • Wichtig: Entwickler schreiben Tests bevor die Klassen implementiert werden. 4. Systemmetapher • Eine Systemmetapher steht für die grundsätzliche Idee hinter der Architektur und ist sowohl Kunden als auch Entwicklern bekannt und typischerweise dem Alltag entnommen. • Sie ersetzt den Architekturentwurf und erleichtert die Kommunikation. • Beispiele für Metaphern sind die Desktop-Metapher für graphische Oberflächen (der Bildschirm entspricht einer Schreibtischoberfläche) oder die Lagerhaus-Metapher für die Verwaltung von Daten in einem System. 5. Einfacher Entwurf • Entwickler wählen immer die einfachste Lösung, die den aktuellen Anforderungen genügt (the simplest thing that could possibly work). • Anforderungen können sich morgen ändern, wodurch zusätzlich eingebaute Flexibilität nutzlos würde. • Sollte später eine generellere Lösung notwendig sein, so wird der Entwurf refaktorisiert. 6. Refaktorisierung • Refaktorisierung dient der Vereinfachung des Entwurfs unter Beibehaltung der Semantik (Eliminierung duplizierten Quelltextes). • Erreicht wird eine Verbesserung der Verständlichkeit und Änderbarkeit des Quelltextes. • Durch die umfangreiche Testsammlung kann mit hoher Wahrscheinlichkeit festgestellt werden, ob sich bei einer Refaktorisierung Fehler eingeschlichen haben. • Der Quelltext selbst soll schließlich in hohem Maße selbsterklärend sein und anderweitige Dokumentation ersetzen. 2.7. EXTREMES PROGRAMMIEREN (XP) 23 7. Programmieren in Paaren • Die Programmierung erfolgt immer in Paaren vor einem Rechner. • Während einer der Entwickler programmiert, prüft der Partner den Quelltext auf logische Fehler und ob der neue Quelltext zur Systemmetapher passt oder ob weitere Tests fehlen. • Die Paarbindung ist dynamisch und fördert so den Austausch von Wissen über verschiedene Aspekte des Softwaresystems. • Nach verschiedenen Studien führt das Programmieren in Paaren zu nur leicht gestiegenen Kosten und deutlich verständlicherem und fehlerfreierem Quelltext. 8. Gemeinsames Eigentum • Der entstandene Quelltext gehört nicht einem bestimmten Entwickler sondern allen Entwicklern in einem Projekt. 9. Kontinuierliche Integration • Neu entwickelter oder geänderter Quelltext wird regelmäßig (mindestens einmal am Tag) in das aktuelle zentrale Archiv der Quelltexte integriert. • Dabei müssen nach der Integration alle vorhandenen Tests bestanden werden. 10. 40-Stunden-Woche • Das Programmieren in Paaren stellt hohe Ansprüche an die Konzentration der Partner. • Daher werden geregelte Arbeitszeiten von 40 Stunden (Richtwert) eingehalten. 11. Kundenvertreter im Team • Da keine Spezifikation vorhanden ist, gibt es viele Rückfragen an den Kunden. • Es sollte daher ein Kundenvertreter für die Entwickler ständig verfügbar sein. 12. Programmierrichtlinien • Alle Entwickler halten sich an feste Programmierrichtlinien, um einheitlichen Quelltext zu erzeugen, der von allen anderen Entwicklern verstanden und leicht geändert werden kann. Bewertung: + Einfaches Prozessmodell für kleinere Softwareprojekte. + Kundenwünsche haben starken Einfluss auf den Entwicklungsprozess. + Steigert die Zufriedenheit der Entwickler. - Fehlen einer expliziten Spezifikation erschwert es, nachträglich neue Entwickler in das Team zu integrieren. - Die Umsetzung von XP ist nur unzureichend dokumentiert. Das Konzept der Systemmetapher ist relativ vage. 24 KAPITEL 2. PROZESSMODELLE Kapitel 3 Unified Modeling Language (UML) Die Unified Modeling Language [3, 18, 26] ist eine Sprache zur Erstellung von Konstruktionsplänen für Softwaresysteme (software blueprint). UML kann benutzt werden, um die Artefakte eines Softwaresystems zu visualisieren, spezifizieren, konstruieren und dokumentieren. UML ist eine objekt-orientierte visuelle Modellierungssprache und nicht gebunden an eine konkrete Implementierungssprache. UML stellt viele verschiedene Diagrammarten zur Verfügung. Die gebräuchlichsten Diagrammarten werden in den folgenden Abschnitten erklärt, wobei wiederum gelegentlich einige Details weggelassen werden. UML-Diagramme sollten jeweils auf einen bestimmten Aspekt fokussieren und irrelevante Details vermeiden, um die Lesbarkeit und Aussagekraft zu erhöhen. Die UML gibt es in verschiedenen Versionen. Zum Zeitpunkt der Erstellung dieses Skripts war die Version 1.3 [16] die aktuelle Version. Bei der Überarbeitung sind einige Anpassungen auf die Version 1.4 [17] vorgenommen worden. 3.1 Notizen Notizen sind textuelle Bemerkungen, die überall in UML-Diagrammen angebracht werden können. Abbildung 3.1 zeigt die Darstellung einer einfachen Notiz. Dies ist eine Notiz. Abbildung 3.1: Eine einfache Notiz in UML Notizen können grundsätzlich überall angebracht werden. Allerdings sollte man sie sparsam verwenden, um Diagramme nicht zu überladen. 3.2 Klassendiagramme Klassendiagramme beschreiben die Struktur der Klassen in einem objekt-orientierten Modell und die strukturellen Beziehungen zwischen Klassen. Eine Klasse ist die Definition der Attribute, Ope25 26 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) rationen und der Semantik für eine Menge von Objekten. Alle Objekte einer Klasse entsprechen dieser Definition. 3.2.1 Klassen Abbildung 3.2 zeigt die Darstellung einer Klasse (class) Vorlesung in der UML. Attribute Operationen Vorlesung Klassenname titel: String semester: String dozent: Person skript: URL toHtml(): String Abbildung 3.2: UML-Klasse Vorlesung Bemerkungen: • Die Klasse wird durch ein Rechteck dargestellt. • Der Name der Klasse steht im oberen Teil des Rechtecks. • Im mittleren Teil des Rechtecks stehen die Attribute der Klasse. • Im unteren Teil des Rechtecks stehen die Operationen, die von der Klasse unterstützt werden. • Die Angabe der Attribute und der Operationen ist jeweils optional. • Klassennamen beginnen typischerweise mit einem Großbuchstaben (wie z.B. Vorlesung) und sind oftmals Hauptwörter. Der Klassenname kann auch einen Pfad enthalten, der ein Paket (package) identifiziert. • Namen von Attributen und Operationen beginnen typischerweise mit einem Kleinbuchstaben (titel, toHtml). Namen von Attributen sind in der Regel Hauptwörter, während Namen von Operationen in der Regel Verben sind. • Ein Attribut wird durch den Namen angegeben, wobei optional der Datentyp des Attributs und ggf. auch ein Initialwert angegeben werden kann. • Eine Operation wird durch den Namen angegeben, wobei optional der Datentyp des Rückgabewerts definiert werden kann. Ebenso können optional die Parameter der Operation angegeben werden, wobei wiederum jeder Parameter durch einen Namen identifiziert wird und optional einen Datentyp und einen Standardwert (default) besitzt. • Zur Identifikation von relevanten Klassen wird oftmals die Hauptwortidentifikation (noun identification technique) benutzt, bei der aus einer Anforderungsspezifikation alle relevanten Hautpwörter extrahiert werden. Klassen beschreiben insbesondere oft – – – – greifbare Dinge oder Stücke aus der Realität (z.B. Vorlesung, Personen, Räume), Rollen (z.B. Dozent, Student), Ereignisse (z.B. Ankunft, Weggang, Anfrage) oder Interaktionen (z.B. Treffen). 27 3.2. KLASSENDIAGRAMME 3.2.2 Sichtbarkeit Für Attribute und Operationen einer Klasse können zusätzlich Sichtbarkeitsangaben gemacht werden (visibility). private protected public Vorlesung -titel: String -semester: String -skript: URL #dozent: Person +getTitle(): String +getSemester(): String +getSkript(): URL +getDozent(): Person +toHtml(): String Abbildung 3.3: UML-Klasse Vorlesung mit Sichtbarkeitsangaben Bemerkungen: • Zugriffsrestriktionen werden durch Symbole vor dem Namen eines Attributs oder einer Operation angezeigt. • Das Symbol + steht für public und bedeutet, dass das Attribut bzw. die Operation für alle sichtbar und benutzbar ist. • Das Symbol # steht für protected und bedeutet, dass das Attribut bzw. die Operation für die Klasse und alle abgeleiteten Klassen sichtbar und benutzbar ist. • Das Symbol − steht für private und bedeutet, dass das Attribut bzw. die Operation nur für die Klasse sichtbar und benutzbar ist. • Das Symbol steht für package und bedeutet, dass das Attribut bzw. die Operation nur innerhalb des Pakets sichtbar und benutzbar ist. 3.2.3 Gültigkeitsbereiche Attribute und Operationen können einerseits den einzelnen Objekten (instance scope) zugeordnet sein oder aber auch einer Klasse selbst (class scope). 28 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) Vorlesung -titel: String -semester: String -skript: URL #dozent: Person -anzahl: Integer +getTitle(): String +getSemester(): String +getSkript(): URL +getDozent(): Person +toHtml(): String +getAnzahl(): Integer Klassenattribut Klassenoperation Abbildung 3.4: UML-Klasse Vorlesung mit Gültigkeitsbereichen Bemerkungen: • Attribute, die nur einmal für alle Instanzen einer Klasse existieren, werden durch unterstreichen markiert. • Operationen, die zur Klasse gehören und nicht zu den Objekten einer Klasse, werden ebenfalls durch unterstreichen markiert. 3.2.4 Generalisierung / Spezialisierung Generalisierung (generalization) und Spezialisierung (spezialization) sind Abstraktionsprinzipien zur Strukturierung eines Modells. Eine Generalisierung ist eine Beziehung zwischen einem allgemeineren und einem speziellen Element, wobei das spezielle Element weitere Eigenschaften hinzufügt und sich kompatibel zum Allgemeinen verhält. Die Vererbung ist ein Programmiersprachenkonzept, mit dem sich Generalisierungen / Spezialisierungen ausdrücken lassen. Veranstaltung Generalisierungen / Spezialisierungen Vorlesung #dozent: Person -skript: URL +getDozent(): Person +getSkript(): URL -titel: String -semester: String +getTitle(): String +getSemester(): String Uebung Seminar Praktikum #leiter: Person -aufgaben: URL Abbildung 3.5: Generalisierung der Vorlesung in eine Veranstaltung Bemerkungen: 29 3.2. KLASSENDIAGRAMME • Generalisierungen / Spezialisierungen werden in UML durch Pfeile dargestellt, wobei die Pfeilspitze zur allgemeineren Klasse zeigt. Die Pfeilspitze ist leer und nicht gefüllt. • Ein Objekt einer spezialisierten Klasse kann ein Objekt einer allgemeineren Klasse in jedem Kontext ersetzen, in dem ein Mitglied der allgemeineren Klasse erwartet wird, aber nicht anders herum. • Eine natürlichsprachliche Prüfung ist die Frage, ob ein Exemplar u einer Spezialisierung U ein Exemplar g einer Generalisierung G ist, d.h. ob u ein g ist (is-a Beziehung). • Die Implementierung von Generalisierungen / Spezialisierungen kann in einer objekt-orientierten Sprache durch Vererbung erfolgen. Vererbung ist eine Implementierungsbeziehung. Gelegentlich ist aber auch die Realisierung mit anderen Mitteln (Komposition) sinnvoll, um beispielsweise eine zu starke Kopplung zwischen den Klassen zu vermeiden. 3.2.5 Abstraktheit Operationen oder auch ganze Klassen können abstrakt (abstract) sein. Eine abstrakte Operation ist zwar innerhalb einer Klasse definiert, muss aber von abgeleiteten Klassen realisiert werden. Eine abstrakte Klasse kann keine Instanzen (Objekte) haben, sondern dient lediglich zur Organisation der Generalisierungs- / Spezialisierungshierarchie. Veranstaltung -titel: String -semester: String +getTitle(): String +getSemester(): String Vorlesung #dozent: Person -skript: URL +getDozent(): Person +getSkript(): URL Uebung abstrakte Klasse Seminar Praktikum #leiter: Person -aufgaben: URL Abbildung 3.6: Veranstaltung als eine abstrakte Klasse Bemerkungen: • Abstrakte Operationen oder Klassen werden durch die kursive Schreibweise der Namen angedeutet. 3.2.6 Aggregation / Komposition Eine Aggregation (aggregation) ist ein Beziehung, deren beteiligte Klassen eine Ganzes-TeileHierarchie darstellen. Eine Komposition (composition) ist eine strengere Form der Aggregation, bei der die Teile vom Ganzen existenzabhängig sind. 30 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) Veranstaltung -titel: String -semester: String +getTitle(): String +getSemester(): String Vorlesung Uebung #dozent: Person -skript: URL +getDozent(): Person +getSkript(): URL Aggregation Seminar Lehrangebot Praktikum #leiter: Person -aufgaben: URL Abbildung 3.7: Lehrangebot besteht aus Veranstaltungen Bemerkungen: • Aggregationen werden in UML durch Linien dargestellt, die an einem Ende eine Raute besitzen. Die Raute steht auf der Seite des Aggregats (also des Ganzen) und ist nicht gefüllt. • Komposition werden in UML wie Aggregationen dargestellt. Zur Unterscheidung wird bei einer Komposition die Raute gefüllt. • Ein typisches Beispiel für eine Komposition ist ein Spielfeld, das aus einer Menge von Feldern besteht. 3.2.7 Assoziationen Assoziationen (associations) beschreiben allgemein Beziehungen zwischen Klassen. Generalisierung / Spezialisierung und Aggregation / Komposition sind spezielle Assoziationen. Entsprechend kann man diese speziellen Assoziationen auch jeweils durch allgemeine Assoziationen ausdrücken. Veranstaltung Assoziationen -titel: String -semester: String +getTitle(): String +getSemester(): String chef 1 Person 1 +name: String mitarbeiter 0..* Rollen haelt Vorlesung arbeitet fuer Multiplizitaet 0..* #dozent: Person -skript: URL +getDozent(): Person +getSkript(): URL Abbildung 3.8: Personen halten Vorlesungen und arbeiten für andere Personen Bemerkungen: 31 3.2. KLASSENDIAGRAMME • Eine Assoziation wird durch eine Linie zwischen den beteiligten Klassen dargestellt. • Der Name einer Assoziation beschreibt ihre Bedeutung (meist ein Verb). • An den Enden der Linie kann die Multiplizität angegeben werden. Die Multiplizität gibt an, mit wie vielen Objekten der gegenüberliegenden Klasse ein Objekt assoziiert sein kann. Die Multiplizität kann als Bandbreite, d.h. durch Minimum und Maximum angegeben werden. Das Symbol ∗ bedeutet beliebig viel. • Eine Assoziation, die eine Klasse mit sich selbst verbindet, heißt rekursiv. • An den Enden der Linien können Rollen angegeben werden, was insbesondere bei rekursiven Assoziationen sinnvoll ist. Assoziationen können manchmal selbst Attribute besitzen. In solchen Fällen spricht man von attributierten Assoziationen. Sie werden in UML durch Assoziationsklassen dargestellt. Veranstaltung -titel: String -semester: String +getTitle(): String +getSemester(): String Person 1 +name: String haelt Vorlesung Uhrzeit +zeit: String 0..* #dozent: Person -skript: URL +getDozent(): Person +getSkript(): URL Assoziationsklasse Abbildung 3.9: Personen halten Vorlesungen zu bestimmten Uhrzeiten Bemerkungen: • Assoziationsklassen werden durch gestrichelte Linien mit der Assoziation verbunden. Gelegentlich sind an einer Assoziation mehr als zwei Klassen beteiligt. Man spricht dann von so genannten mehrgliedrigen Assoziationen. 32 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) Raum +nummer: String Veranstaltung -titel: String -semester: String +getTitle(): String +getSemester(): String Person +name: String Vorlesung #dozent: Person -skript: URL +getDozent(): Person +getSkript(): URL mehrgliedrige Assoziation Abbildung 3.10: Personen halten Vorlesungen in einem bestimmten Raum Bemerkungen: • Mehrgliedrige Assoziationen werden durch Linien dargestellt, die durch eine zentrale Route miteinander verbunden sind. 3.2.8 Schnittstellen und Realisierungen Schnittstellen (interfaces) beschreiben einen ausgewählten Teil des extern sichtbaren Verhaltens von Modellelementen. Die Schnittstellenklassen sind abstrakt und definieren ausschließlich abstrakte Operationen. Schnittstelle <<interface>> Person Realisierung chef 1 +getName(): String Veranstaltung -titel: String -semester: String +getTitle(): String +getSemester(): String Mitarbeiter 1 -name: String mitarbeiter 0..* haelt Vorlesung arbeitet fuer 0..* #dozent: Mitarbeiter -skript: URL +getDozent(): Mitarbeiter +getSkript(): URL Abbildung 3.11: Mitarbeiter realisieren die Schnittstelle Person Bemerkungen: • Eine Schnittstellenklasse wird durch den Stereotyp interface gekennzeichnet. • Die Realisierung einer Schnittstellenklasse wird durch einen gestrichelten Pfeil (analog zur Generalisierung / Spezialisierung) angezeigt. 33 3.2. KLASSENDIAGRAMME Schnittstellen können alternativ auch kompakter ohne weitere Details durch einen einfachen Kreis dargestellt werden. Veranstaltung Person Realisierung Schnittstelle chef 1 Mitarbeiter -titel: String -semester: String +getTitle(): String +getSemester(): String 1 -name: String mitarbeiter 0..* haelt Vorlesung arbeitet fuer 0..* #dozent: Mitarbeiter -skript: URL +getDozent(): Mitarbeiter +getSkript(): URL Abbildung 3.12: Mitarbeiter realisieren die Schnittstelle Person Bemerkungen: • Eine Linie, die einen solchen Kreis mit einem Modellelement verbindet, signalisiert, dass das Modell die Schnittstelle realisiert. 3.2.9 Abhängigkeiten Eine Abhängigkeit (dependency) ist eine Beziehung zwischen zwei Modellelementen, die anzeigt, dass eine Änderung in dem einen unabhängigen Element eine Änderung in dem anderen abhängigen Element notwendig macht. Abhängigkeiten beziehen sich auf die Modellelemente selbst und nicht auf eventuelle Instanzen. • Abhängigkeiten werden durch einen gestrichelten Pfeil dargestellt, wobei der Pfeil vom abhängigen Element auf das unabhängige Element verweist. • Abhängigkeiten entstehen oftmals durch Klassen, die eine bestimmte Schnittstelle einer anderen Klasse benutzen. Eine Änderung der Schnittstelle kann Änderungen an den benutzenden Klassen erfordern. 3.2.10 Objekte Objekte sind konkrete Ausprägungen (Instanzen) von Klassen. Sie stellen einen Schnappschuss eines Systems zu einem bestimmten Zeitpunkt dar. Die Abbildung 3.13 zeigt eine konkrete Ausprägung der beiden Klassen Mitarbeiter und Vorlesung und der Assoziation haelt. 34 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) c01: Vorlesung titel = "Informatik C" semester = "WS 2001/2002" skript = "http://www.vorlesungen.uos.de/informatik/c01/" js: Mitarbeiter name = "Juergen Schoenwaelder" sec01: Vorlesung haelt haelt haelt titel = "Sicherheit in vernetzten Systemen" semester = "WS 2001/2002" skript = "http://www.vorlesungen.uos.de/informatik/sec01/" bs01: Vorlesung titel = "Betriebssysteme" semester = "WS 2001/2002" skript = "http://www.vorlesungen.uos.de/informatik/bs01/" Abbildung 3.13: Konkrete Ausprägung der Klassen Vorlesung und Mitarbeiter • Objekte werden durch Rechtecke dargestellt. • Der Name eines Objekts wird unterstrichen im oberen Teil des Rechtecks angegeben (zur Unterscheidung von Klassen). • Die Angabe der Klasse im Namensteil ist optional. • Objektnamen beginnen typischerweise mit einem Kleinbuchstaben. • Belegungen der Attribute können optional im unteren Teil des Rechtecks dargestellt werden. Für jedes Attribut existiert dann eine Zeile in der Form Attributename = Wert. • Beziehungen zwischen Objekten werden durch einfache Linien dargestellt. Das Diagramm von Abbildung 3.13 kann entsprechend auch viel kompakter dargestellt werden, wenn die Details nicht wesentlich sind (Abbildung 3.14). c01: Vorlesung js: Mitarbeiter haelt haelt haelt sec01: Vorlesung bs01: Vorlesung Abbildung 3.14: Kompakte Darstellung eines Objektdiagramms 35 3.2. KLASSENDIAGRAMME Es ist auch möglich, so genannte Multiobjekte (Abbildung 3.15) darzustellen, die für eine Menge von Objekten stehen. js: Mitarbeiter haelt mehrereVorlesungen Abbildung 3.15: Kompakte Darstellung eines Objektdiagramms mit Multiobjekten 3.2.11 Pakete Pakete (packages) sind Ansammlungen von Modellelementen belieben Typs mit denen das Gesamtmodell in kleinere überschaubare Einheiten gegliedert wird. Ein Paket definiert einen Namensraum. Alle im Namensraum enthaltenen Elemente müssen eindeutig sein. Ein Paket Vorlesungsverwaltung für die bisher eingeführten Klassen ist in Abbildung 3.16 dargestellt. Vorlesungsverwaltung Vorlesung Mitarbeiter Uebung Seminar Praktikum Abbildung 3.16: Paketdiagramm Bemerkungen: • Ein Paket wird in Form eines Aktenregisters dargestellt. Innerhalb dieses Symbols steht der Name des Pakets. Werden innerhalb des Symbols Modellelemente angezeigt, so steht der Name des Pakets auf der Registerlasche. • Jedes Element gehört zu genau einem Paket. • Pakete können andere Pakete enthalten (Hierarchie). • Das alleroberste Paket ist implizit vorhanden und enthält das gesamte Modell. • Der Name eines Pakets beginnt typischerweise mit einem Großbuchstaben. • Da Pakete andere Pakete enthalten können, wird ein Modellelement eindeutig durch den Pfad der Paketnamen und den Namen des Modellelements identifiziert. • Die Sichtbarkeit der in einem Paket enthaltenen Elemente kann wiederum durch Sichtbarkeitssymbole angegeben werden. • Zwischen Paketen können Abhängigkeiten existieren, die oftmals durch das Importieren von Paketelementen entstehen. 36 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) • Pakete können auch Generalisierungen/Spezialisierungen von anderen Paketen sein. Abhaengigkeit GUI Generalisierung/ Spezialisierung Browser Mac GUI Windows GUI Unix GUI Abbildung 3.17: Paketdiagramm mit Abhängigkeiten und Generalisierung/Spezialisierung 37 3.3. SEQUENZDIAGRAMME 3.3 Sequenzdiagramme Ein Sequenzdiagramm (sequence diagram) zeigt eine Reihe von Nachrichten, die eine ausgewählte Menge von Objekten in einer zeitlich begrenzten Situation austauschen. Der zeitliche Verlauf der Nachrichten steht beim Sequenzdiagramm im Vordergrund. s: Caller : Switch r: Caller liftReceiver setDialTone() * dialDigit(d) «create» c: Conversation ring() liftReceiver connect(r,s) connect(r) connect(s) hangUpReceiver disconnect(r) disconnect(s) disconnect(r,s) «destroy» Abbildung 3.18: Sequenzdiagramm für eine Telefonverbindung Bemerkungen: • Die Zeitachse verläuft von oben nach unten. • Objekte werden wie in Objektdiagrammen durch Rechtecke dargestellt. • Ausgehend vom Objekt zeigt eine gestrichelte vertikale Lebenslinie die Dauer der Existenz des Objekts an. • Das Ende der Existenz eines Objekts wird durch ein Kreuz am Ende der Lebenslinie hervorgehoben. • Breite vertikale Balken signalisieren welche Objekte gerade aktiv sind und deuten so den Kontrollfluss an. • Zwischen Objekten (symbolisch zwischen ihren Balken bzw. Lebenslinien) werden Nachrichten ausgetauscht. • Zum Erzeugen von Objekten und Löschen von Objekten werden Nachrichten mit dem Stereotypen create bzw. destroy verwendet. • Nachrichten, die wiederholt auftreten, können durch einen Multiplikator gekennzeichnet werden. Abbildung 3.19 zeigt die verschiedenen Nachrichtentypen, wie sie typischerweise in Sequenzdiagrammen vorkommen. 38 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) anObject signal() <<create>> anotherObject call() return <<destroy>> recursive() Abbildung 3.19: Nachrichtentypen in Sequenzdiagrammen Bemerkungen: • Asynchrone Nachrichten, die keine Antwort erzwingen, werden durch offene Pfeile dargestellt. • Eine Nachricht, die einem Operationsaufruf entspricht, wird durch einen gefüllten Pfeil dargestellt. • Der Abschluss einer Operation kann optional durch einen Pfeil mit einer gestrichelten Linie dargestellt werden. Fehlt dieser Pfeil, so ergibt sich das zeitliche Verhalten aus der Länge des Balken des aufgerufenen Objekts. • Rekursive Aufrufe desselben Objekts deutet man durch eine Überlagerung von Aktivitätsbalken an. 39 3.4. KOLLABORATIONSDIAGRAMME 3.4 Kollaborationsdiagramme Ein Kollaborationsdiagramm (collaboration diagram) zeigt eine Menge von Interaktionen zwischen ausgewählten Objekten in einer bestimmten Situation. Dabei werden die Beziehungen zwischen den Objekten und ihre Topographie betont. Kollaborationsdiagramme beschreiben dieselben Sachverhalte wie Sequenzdiagramme, allerdings aus einer anderen Perspektive. s: Caller 1.1: setDialTone() 1: liftReceiver 2: *dialDigit(d) 2.1.3: connect() 2.1.5: disconnect() : Switch 2.1: <<create>> 2.2: <<destroy>> c: Conversation 2.1.2: connect() 2.1.7: disconnect() 2.1.1: ring() 2.1.4: connect() 2.1.6: disconnect() 2.1.1.1: liftReceiver 2.1.4.1: hangUpReceiver r: Caller Abbildung 3.20: Kollaborationsdiagramm für eine Telefonverbindung Bemerkungen: • Der zeitliche Zusammenhang der Nachrichten wird durch die hierarchische Nummerierung der Nachrichten ausgedrückt. • Neben dem Namen der Nachricht können auch Parameter angegeben werden. • Sequenzdiagramme können in Kollaborationsdiagramme transformiert werden und Kollaborationsdiagramme können in Sequenzdiagramme transformiert werden. • Der Fokus bei den Diagrammtypen und damit die Anzahl der Details ist jedoch in der Regel deutlich unterschiedlich. 40 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) 3.5 Zustandsdiagramme Zustandsdiagramme (statechart diagrams) veranschaulichen einfache Zustandsautomaten. Dabei stehen die Zustände im Vordergrund der Betrachtung. Die Transitionsmöglichkeiten zwischen den Zuständen werden durch gerichtete Pfeile angedeutet. Das folgende Beispiel zeigt einen Zustandsautomaten für eine einfache Klimasteuerungsanlage. powerOff powerOn idle tooCold tooHot atTemp atTemp cooling tooHot heating tooCold Abbildung 3.21: Zustandsdiagramm einer Klimaregelungsanlage Bemerkungen: • Der Startzustand wird durch einen ausgefüllten Kreis dargestellt. • Die Zielzustände werden durch einen umringten gefüllten Kreis dargestellt. • An den Transitionspfeilen können die Namen der auslösenden Ereignisse (events) angebracht werden. • Zustände können selbst wiederum interne Zustandsdiagramme enthalten. 41 3.6. AKTIVITÄTSDIAGRAMME 3.6 Aktivitätsdiagramme Aktivitätsdiagramme (activity diagrams) beschreiben den Kontrollfluss in einem System. Im Gegensatz zu Sequenzdiagrammen, die den Nachrichtenaustausch zwischen Objekten darstellen, beschreiben Aktivitätsdiagramme den dynamischen Ablauf von Aktivitäten. Eine Aktivität ist eine fortlaufende nicht-atomare Ausführung in einer Zustandsmaschine. Das folgende Aktivitätsdiagramm zeigt den Ablauf der Aktivitäten beim Kaffeekochen: Durst auf Kaffee Filter einsetzen Wasser einfuellen Kaffee einfuellen Tassen suchen Kaffee kochen Kaffee eingiessen Kaffee trinken Abbildung 3.22: Kaffeepause als Aktivitätsdiagramm Bemerkungen: • Der Startzustand wird durch einen ausgefüllten Kreis dargestellt. • Die Zielzustände werden durch einen umringten gefüllten Kreis dargestellt. • Eine Aktivität wird durch ein abgerundetes Rechteck dargestellt. 42 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) • An breiten horizontalen Linien können Kontrollflüsse aufgeteilt und wieder zusammengeführt werden. Durch die Einführung von Schwimmlinien (swim lanes) können Zuständigkeiten ausgedrückt werden: Chef Mitarbeiter1 Mitarbeiter2 Durst auf Kaffee Filter einsetzen Wasser einfuellen Kaffee einfuellen Tassen suchen Kaffee kochen Kaffee eingiessen Kaffee trinken Abbildung 3.23: Kaffeepause als Aktivitätsdiagramm mit Swimlanes 43 3.7. ANWENDUNGSFALLDIAGRAMME 3.7 Anwendungsfalldiagramme Ein Anwendungsfall (use case) beschreibt eine Menge von Aktivitäten eines Systems aus der Sicht seiner Akteure (actor). Zeitschrift registrieren Zeitschrift studieren Mitarbeiter Umlaufzettel erstellen Sekretariat Bibliothek Zeitschrift archivieren Abbildung 3.24: Anwendungsfall Zeitschriftenumlauf Kreditkartensystem KreditkartenTransaktion Erstellung der Rechnung Kunde Pruefen der Transaktionen Verwaltung des Kundenkontos PrivatKunde FirmenKunde Abbildung 3.25: Anwendungsfall Kreditkartensystem Bemerkungen: Haendler Bank 44 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) • Ein Anwendungsfall wird durch eine Ellipse dargestellt, die den Namen des Anwendungsfalls trägt. • Zu jedem Anwendungsfall gehört ein Text, der den Anwendungsfall genauer beschreibt. • Akteure werden durch kleine Strichmännchen dargestellt. • Ein Anwendungsfall wird stets durch einen Akteur initiiert. • In einem Anwendungsfalldiagramm können wiederum die bekannten Assoziationen dargestellt werden. • Systemgrenzen werden durch ein Rechteck dargestellt. 3.8 Komponentendiagramme Eine Komponente (component) stellt ein physisches Stück Programmcode dar (Quelltext, Binärcode, Bibliotheken, ausführbare Programme). Komponentendiagramme (component diagrams) zeigen die Beziehungen zwischen den Komponenten. «document» «file» nvi manual /etc/vi.exrc «library» libc.so.6 «executable» nvi «library» «library» ld.so.2 ncurses.so.5 Abbildung 3.26: Komponentendiagramm für ein ausführbares Programm 45 3.9. VERTEILUNGSDIAGRAMME «file» «file» gsnmp.h glib.h «file» scli.h «file» «file» «file» basic.c system.c scli.c Abbildung 3.27: Komponentendiagramm für Quelltextmodule Bemerkungen: • Eine Komponente wird als Rechteck dargestellt, das am linken Rand zwei kleine Rechtecke trägt. • Innerhalb des Rechtecks wird der Name der Komponente angegeben. • Komponenten beschreiben im Gegensatz zu Paketen die physische Sicht auf ein Softwaresystem. • Es gibt vordefinierte Stereotypen für ausführbare Programme (executable), Bibliotheken (library), Dateien (file), Tabellen (table) und Dokumente (document). • Abhängigkeiten zwischen Komponenten werden wie üblich durch Pfeile mit gestrichelten Linien angezeigt. 3.9 Verteilungsdiagramme Ein Knoten (node) ist ein zur Laufzeit physisch vorhandenes Objekt, das über Betriebsmittel (z.B. Prozessorleistung, Speicher) verfügt. Verteilungsdiagramme (deployment diagrams) visualisieren, welche Komponenten und Objekte auf welchen Knoten (Prozessoren, Computer) installiert sind und welche Kommunikationsbeziehungen bestehen. 46 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) external network firewall workstation local network server workstation «executable» «executable» apache nvi Abbildung 3.28: Verteilungsdiagramm Bemerkungen: • Ein Knoten wird durch einen Quader dargestellt. • Knoten besitzen Namen, die typischerweise kurze Hauptworte sind. • Knoten, die miteinander kommunizieren, werden durch Assoziationslinien miteinander verbunden. • Knoten können andere UML-Elemente enthalten. 3.10 Beispiel: Fahrstuhl Betrachten Sie als Beispiel die Steuerungslogik eines Fahrstuhls: • Das Gebäude hat m Etagen. • Im Fahrstuhl befinden sich m Knöpfe für die einzelnen Etagen. • Die unterste Etage hat einen Knopf up. • Die oberste Etage hat einen Knopf down. • Alle anderen Etagen haben jeweils einen Knopf up und einen Knopf down. • Knöpfe werden von der Steuerungslogik beleuchtet, nachdem sie gedrückt wurden und solange der Fahrtstuhl die entsprechende Etage noch nicht erreicht hat. • Der Fahrstuhl bewegt sich nur sofern dies notwendig ist. 47 3.10. BEISPIEL: FAHRSTUHL • Die Steuerungslogik muss nach dem Erreichen einer Ziel-Etage die Tür öffnen und wieder schließen. • Die Fahrstuhltür bleibt geschlossen, solange der Fahrstuhl nicht benutzt wird. Zuerst sollte man sich über die Aktivitäten klar werden. Dazu erstellt man ein geeignetes Anwendungsfalldiagramm (Abbildung 3.29). Fahrstuhl feststellen, ob ein Knopf betaetigt wurde Knopfbeleuchtung kontrollieren Benutzer Fahrstuhl bewegen Steuerungslogik Tuer oeffnen und schliessen Abbildung 3.29: Anwendungsfalldiagramm für eine Fahrstuhlsteuerung Es empfiehlt sich als nächstes ein grobes Klassendiagramm zu entwerfen (Abbildung 3.30). Offenbar benötigt man eine Klasse für den Fahrstuhl (Elevator), eine Klasse für die Steuerungslogik (ElevatorController) und Klassen für die verschiedenen Knöpfe. Da man zwei verschiedene Arten von Knöpfen hat, empfiehlt sich die Definition einer abstrakten Oberklasse (Button) von der die Klassen für Knöpfe im Fahrstuhl (ElevatorButton) und Knöpfe auf den Fluren (FloorButton) abgeleitet werden. Die Klasse Door repräsentiert die Tür eines Fahrstuhls. Die Komposition in Abbildung 3.30 mit der Multiplizität 1:1 drückt aus, dass eine Fahrstuhltür immer zu einem Fahrstuhl gehört und ein Fahrstuhl genau eine Tür besitzt. Die Assoziationen controls beschreibt, welche Elemente die Steuerungslogik kontrolliert. Die Assoziation updates drückt aus, dass Knöpfe jeweils einer Steuerungslogik zugeordnet sind. Die Abbildung 3.31 zeigt ein Sequenzdiagramm für die Fahrstuhlsteuerung. Eine Person (bisher nicht weiter modelliert) drückt auf einen Knopf im Fahrstuhl. Dieser Vorgang findet asynchron statt. Der Knopf meldet seinen Zustandswechsel an die Steuerungslogik. Die Steuerungslogik veranlasst die Beleuchtung des Knopfes und instruiert den Fahrstuhl sich in die entsprechende Etage zu 48 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) Elevator 1 +currentFloor: Integer +direction: Boolean +move(floor:Integer) 1 controls controls Door +closed: Boolean = true +open() +close() ElevatorController +update() updates Button +illuminated: Boolean = false +illuminate() +cancel() ElevatorButton +dstFloorNumber: Integer FloorButton +direction: Boolean +floorNumber: Integer Abbildung 3.30: Klassendiagramm für eine Fahrstuhlsteuerung bewegen. Ist die Etage erreicht, so wird die Beleuchtung des Knopfes beendet und die Tür geöffnet. Nach einer kurzen Wartezeit wird die Tür wieder geschlossen. aPassenger anElevatorButton aController anElevator aDoor pressed update illuminate move cancel open close Abbildung 3.31: Sequenzdiagramm für eine Fahrstuhlsteuerung Analog zum Sequenzdiagramm lässt sich ein Kollaborationsdiagramm angeben (Abbildung 3.32). 49 3.10. BEISPIEL: FAHRSTUHL controls aDoor aPassenger 1: pressed() 1.1.4: open() 1.1.5: close() 1.1.1: illuminate() 1.1.3: cancel() updates aController 1.1: update() 1.1.2: move() anElevatorButton controls anElevator Abbildung 3.32: Kollaborationsdiagramm für eine Fahrstuhlsteuerung 50 KAPITEL 3. UNIFIED MODELING LANGUAGE (UML) Kapitel 4 Entwurfsmuster Beim Entwurf von (objektorientierten) Softwaresystemen gibt es häufig wiederkehrende Teilprobleme zu lösen. Erfahrene Softwaredesigner greifen zur Lösung dieser Probleme gern auf bereits erfolgreich angewendete Lösungen zurück, die ggf. weiter verfeinert werden und generalisiert werden. Entwurfsmuster (design pattern) [10] versuchen bekannte gute Lösungen für spezifische Entwurfsprobleme zu dokumentieren und auf diese Weise die Expertise von Entwurfsexperten zugänglich zu machen und so die Wiederverwendung von guten Entwurfsmustern zu erhöhen. Entwurfsmuster helfen Entwurfsalternativen zu bewerten und Lösungen zu vermeiden, die sich negativ auf die Wiederverwendbarkeit und Erweiterbarkeit eines Softwaresystems auswirken. Ein Entwurfsmuster besitzt vier essentielle Elemente: 1. Name des Entwurfsmusters 2. Problembeschreibung 3. Beschreibung der Lösung 4. Konsequenzen aus der Anwendung des Entwurfsmusters Entwurfsmuster lassen sich in drei Kategorieren einteilen: 1. Entwurfsmuster, die sich mit dem Erzeugen von Objekten befassen (creational design pattern). 2. Entwurfsmuster, die sich mit den strukturellen Aspekten der Organisation von Klassen und Objekten befassen (structural design pattern). 3. Entwurfsmuster, die sich mit den Interaktionen zwischen Objekten und der Verteilung der Funktionalität zwischen Objekten befassen (behavioral design pattern). Zur Dokumentation von Entwurfsmustern wird eine Schablone vorgeschlagen, die aus folgenden Elementen besteht [10]: • Name des Entwurfsmusters: Der Name und die Klassifikation des Entwurfsmusters. Ein guter Name ist wichtig, da er das Vokabular der Entwickler prägt. 51 52 KAPITEL 4. ENTWURFSMUSTER • Aufgabe: Kurze Beschreibung, was ein Entwurfsmuster leistet und welche spezielle Entwurfsaufgabe es löst. • Synonyme: Alternative Bezeichnungen für das Entwurfsmuster. • Motivation: Beschreibung eines Szenarios, das das Entwurfsproblem verdeutlicht und erklärt, wie das Entwurfsmuster das Problem löst. • Anwendbarkeit: Beschreibung der Situationen in denen das Entwurfsmuster anwendbar ist, welche Vorteile die Anwendung bietet und wie man solche Situationen erkennt. • Struktur: Eine graphische Darstellung der Struktur in Form eines Klassendiagramms. • Teilnehmer: Beschreibung der Elemente, die an dem Entwurfsmuster beteiligt sind. • Zusammenarbeit: Erklärung wie die Elemente eines Entwurfsmusters zusammenarbeiten. • Konsequenzen: Beschreibung die Konsequenzen aus der Anwendung des Entwurfsmustern. • Implementation: Darstellung von möglichen Alternativen bzw. möglichen Problemen bei der Implementierung. • Beispiel: Beschreibung einer Beispielimplementation. • Anwendungen: Kurzbeschreibung von realen Systemen, in denen das Entwurfsmustern eingesetzt wird. • Verwandte Entwurfsmuster: Verweise auf verwandte Entwurfsmuster. Gute Entwurfsmuster dienen neben dem Hauptziel der Wiederverwendung guter Lösungen dazu, die Qualität eines Softwareentwurfs zu verbessern. Ziel ist es, Softwareentwürfe flexibel und erweiterbar zu halten, da sich die Anforderungen im Lebenslauf der Software ändern werden (design for change). Um dieses Ziel zu erreichen benötigt man geeignete Abstraktionen und Mechanismen um die Abhängigkeiten zwischen Objekten und Softwarekomponenten zu kontrollieren. Wichtige Maßnahmen sind nach [10]: • Programmiere zu einem Interface und nicht zu einer Implementation. • Bevorzuge die Komposition von Objekten gegenüber der Vererbung. Zu den Entwurfsmustern existieren einige verwandte Konzepte: 53 • Ein Gegenmuster (Anti-Pattern) beschreibt, wie man von einem Entwurfsproblem zu einer schlechten Lösung kommt. Ein gutes Anti-Pattern erklärt, warum die schlechte Lösung zunächst attraktiv ist, warum sie letztlich nicht gut ist und welche Entwurfsmuster stattdessen anwendbar sind. • Idiome sind im Vergleich zu Design Pattern sehr einfache wiederkehrende Muster, die typischerweise spezifisch für eine konkrete Programmiersprache sind. 54 KAPITEL 4. ENTWURFSMUSTER 4.1 Factory Method Definiert eine Schnittstelle zur Erzeugung eines Objekts, wobei abgeleitete Klassen entscheiden können, von welcher Klasse tatsächlich Objekte instantiiert werden. Synonyme Cirtual Constructor Motivation xxx Anwendbarkeit • Wenn eine Klasse nicht vorhersehen kann, von welchen Klassen sie Objekte erzeugen muss. • Wenn eine Klasse den abgeleiteten Klassen die Freiheit geben will, die Objekte zu spezifizieren, die es erzeugt. • Wenn Klassen Verantwortung an eine oder mehrere Hilfsklassen delegieren und das Wissen über die verschiedenen Hilfsklassen lokalisiert werden soll. Struktur Product «interface» Creator +FactoryMethod(): Product ConcreteProduct ConcreteCreator +FactoryMethod(): ConcreteProduct Abbildung 4.1: Klassendiagramm für das Factory Method Entwurfsmuster 55 4.2. ITERATOR 4.2 Iterator Aufgabe Das Entwurfsmuster Iterator erlaubt es, die Elemente eines aggregierenden Objekts sequentiell zu durchlaufen, ohne die zugrundelegende Implementierung zu freizulegen. Synonyme Cursor Motivation Oftmals ist es notwendig, die Elemente eines zusammengesetzten Objekts (Aggregat) durchlaufen zu können, ohne die interne Struktur des Aggregats offenlegen zu wollen. Es kann auch sinnvoll sein, nachträglich verschiedene Arten des Durchlaufs zu unterstützen, ohne die Implementierung des Aggregats selbst zu verändern. Anwendbarkeit • Wenn die Elemente eines Aggregats durchlaufen werden müssen ohne das die innere Struktur des Aggregats freigelegt wird. • Wenn ein Aggregat auf verschiedene Arten durchlaufen werden kann. • Wenn eine einheitliche Schnittstelle zum Durchlaufen verschiedener Aggregate erforderlich ist (polymorphic iteration). Struktur «interface» Iterator «interface» Aggregate +first() +next() +isDone() +currentItem() +createIterator() ConcreteAggregate 1 iteratesOver * ConcreteIterator +createIterator() return new ConcreteIterator(self); Abbildung 4.2: Klassendiagramm für das Iterator Entwurfsmuster 56 KAPITEL 4. ENTWURFSMUSTER Teilnehmer • Ein Iterator definiert eine Schnittstelle zum Durchlaufen und Zugriff auf Elemente. • Ein ConcreteIterator implementiert die Iterator Schnittstelle und kennt die aktuelle Position im Aggregate. • Ein Aggregate definiert eine Schnittstelle zum Erzeugen eines Iterators. • Ein ConcreteAggregate implementiert die Schnittstelle zum Erzeugen eines passenden Iterators. Zusammenarbeit • Ein ConcreteIterator kennt das aktuelle Objekt im Aggregate und kann das nächste Objekt ermitteln. Konsequenzen • Es lassen sich verschiedenen Arten des Durchlaufs realisieren indem man weitere Iteratoren implementiert. • Iteratoren vereinfachen die Schnittstelle des Aggregats. • Mehrere Durchläufe können aktiv sein, da jeder Iterator einen eigenen Zustand besitzt. Implementation • Interne Iteratoren (internal iterators) führen automatisch eine Operation auf allen Elementen eines Aggregats aus. Externe Iteratoren (external iterators) verlangen, dass der Benutzer des Iterators das nächste Element explizit verlangt. Externe Iteratoren sind flexibler, da man beispielsweise zwei Aggregate damit vergleichen kann. Interne Iteratoren sind jedoch manchmal nützlich, da sie die Iterationslogik komplett implementieren und dadurch auch effizienter sind. Interne Iteratoren sind meist in Programmiersprachen realisiert, die closures oder continuations unterstützen. • Man kann den Iterationsalgorithmus entweder im Iterator realisieren oder im Aggregat. Im letzteren Fall spricht man auch von einem Cursor. Die Implementation des Iterationsalgorithmus im Iterator hat den Vorteil, dass verschiedene Algorithmen leicht realisiert werden können und den Nachteil, dass der Iterator Kenntnisse über die Struktur des Aggregats besitzt. Bei einem Cursor kann das notwendige Wissen des Iterators über die Struktur minimiert werden. • Robuste Iteratoren stellen sicher, dass Änderungen am Aggregat nicht zu unerwartetem Verhalten führen können. • Es kann sinnvoll sein, zusätzlich Operationen für Iteratoren zu definieren, beispielsweise eine Previous Operation für geordnete Aggregate. • Ein NullIterator ist ein degenerierter Iterator, der immer fertig ist, d.h. die Operation isDone ist immer wahr. 57 4.2. ITERATOR Beispiel Object «interface» Iterator «interface» Enumeration +hasNext(): boolean +next(): Object +remove(): void +hasMoreElements(): boolean +nextElement(): Object «interface» ListIterator StringTokenizer +StringTokenizer(str:String) +StringTokenizer(str:String,delim:String) +StringTokenizer(str:String,delim:String,returnDelims:boolean) +countTokens(): int +hasMoreElements(): boolean +hasMoreTokens(): boolean +nextElement(): Object +nextToken(): String +nextToken(delim:String): String +add(o:Object): void +hasNext(): boolean +hasPrevious(): boolean +next(): Object +nextIndex(): int +previous(): Object +previousIndex(): int +remove(): void +set(o:Object): void Abbildung 4.3: Realisierung des Iterator Entwurfsmusters in Java Anwendungen Das Iterator Entwurfsmuster ist in vielen objektorientierten Systemen implementiert. Beispiele sind die Smalltalk Collection Klassen oder die Iterator Schnittstelle in Java. Verwandte Entwurfsmuster Composite, Factory Method 58 KAPITEL 4. ENTWURFSMUSTER 4.3 Composite Aufgabe Darstellung einer ist-Teil-von Hierarchie, bei der einzelne Objekte genauso wie Kompositionen von Objekten behandelt werden. Motivation Graphische Editoren stellen eine Zahl primitiver Graphikobjekte bereit. Benutzer können diese Objekte in der Regel gruppieren und dadurch zusammengesetzte Objekte erzeugen. Eine mögliche Implementierungstechnik ist die Definition von Klassen für die primitiven Objekte und Klassen für zusammengesetzte Objekte. Der Nachteil dieses Ansatzes ist, dass im Programm ständig Unterscheidungen zwischen primitiven und zusammengesetzten Objekten notwendig sind, obwohl der Benutzer die Objekte gleich behandelt (z.B. draw, delete). Das Entwurfsmuster Composite löst dieses Problem indem eine abstrakte Klasse eingeführt wird, von der Klassen für primitive als auch zusammengesetzte Objekte abgeleitet werden. Anwendbarkeit • Wenn ist-Teil-von Beziehungen dargestellt werden müssen. • Wenn für der Benutzer Kompositionen und elementare Objekte gleichförmig benutzen möchte, ohne die Unterschiede kennen zu müssen. Struktur «interface» Component contains +operation() +add(Component) +remove(Component) +getChild(int) Leaf +operation() Composite +operation() +add(Component) +remove(Component) +getChild(int) for all g in children { g.operation() } Abbildung 4.4: Klassendiagramm für das Composite Entwurfsmuster 4.3. COMPOSITE 59 Teilnehmer • Ein Component definiert die Schnittstelle für die Objekte in der Komposition, definiert eine Schnittstelle zum Zugriff auf Teile und optional eine Schnittstelle zum Zugriff auf das enthaltende Objekt. Die Component-Klasse implementiert das gemeinsame Standard-Verhalten. • Ein Leaf repräsentiert ein Element in der Komposition, das keine anderen Elemente enthalten kann und definiert das Verhalten von primitiven Objekten in der Komposition. • Ein Composite repräsentiert Elemente in der Komposition, die andere Elemente enthalten können. Die Composite-Klasse verwaltet die Referenzen auf enthaltene Elemente und implementiert Operationen zum Zugriff auf die enthaltenen Elemente. Zusammenarbeit Programme interagieren mit den Elementen in einer Komposition nur über die Component-Schnittstelle. Leaf-Objekte implementieren eine Operation in der Regel direkt. Composite-Objekte implementieren Funktionen in der Regel dadurch, dass die Operation an alle enthaltenen Objekte weitergeleitet wird. Dabei können vor und nach dem Weiterleiten noch zusätzliche Berechnungen stattfinden. Konsequenzen • Das Pattern definiert eine Hierarchie bestehend aus primitiven und zusammengesetzten Objekten. Primitive Objekte können in zusammengesetzten Objekten enthalten sein, die selbst wieder Teil eines zusammengesetzten Objekts sein können. • Programme benutzen die Objekte ohne die Unterschiede zwischen primitiven und zusammengesetzten Objekten kennen zu müssen. • Neue primitive und zusammengesetzte Objekte können leicht eingeführt werden. • Es gibt keine Möglichkeit, die in einem zusammengesetzten Objekt enthaltenen Objekte auf eine Teilmenge zu beschränken. Implementation • xxx Beispiel xxx Anwendungen Das Entwurfsmuster Composite wird in vielen graphischen Oberflächensystemen angewendet. Eine andere Anwendung findet man in Compilern, die den vom Parser generierten Baum durch ein Composite darstellen. 60 Verwandte Entwurfsmuster Iterator KAPITEL 4. ENTWURFSMUSTER 61 4.4. OBSERVER 4.4 Observer Aufgabe Das Entwurfsmuster Observer definiert eine 1:n Abhängigkeit zwischen Objekten mit dem Ziel Änderungen am Zustand eines Objekt automatisch allen abhängigen Objekten mitzuteilen, damit sich die abhängigen Objekte aktualisieren können. Synonyme Dependents, Publish-Subscribe Motivation Es ist oft erforderlich, die Konsistenz zwischen kooperierenden Objekten zu bewahren. Es ist dabei erstrebenswert, die Klassen zu entkoppeln, damit neue abhängige Objekte eingeführt werden können, ohne bestehende Objekte ändern zu müssen. Das Entwurfsmuster Observer führt das Konzept des Subject und des Observer ein. Ein Subject kann mehrere Observer besitzen, die bei einer Zustandsänderung des Subjects benachrichtigt werden und dann ihren Zustand synchronisieren. Anwendbarkeit • Wenn eine Änderung eines Objekts Änderungen in anderen Objekten erfordert. • Wenn ein Objekt in der Lage sein soll, andere Objekte zu benachrichtigen, ohne Annahmen über die Art der anderen Objekte zu machen. Struktur «interface» Subject 1 observes * «interface» Observer #observers +attach(Observer) +detach(Observer) +notify() #subject +update() for all o in observers { o.update() } observerState = subject.getState() ConcreteSubject +subjectState +getState() +setState() ConcreteObserver +observerState +update() Abbildung 4.5: Klassendiagramm für das Observer Entwurfsmuster Teilnehmer • Ein Subject kennt seine Observer und stellt ein Interface bereit, über das sich Observer anund abmelden können. 62 KAPITEL 4. ENTWURFSMUSTER • Ein Observer stellt eine Schnittstelle bereit, über die er über Änderungen des Subjects benachrichtigt werden kann. • Ein ConcreteSubject besitzt einen Zustand und benachrichtigt die Observer, wenn der Zustand sich ändert. • Ein ConcreteObserver beinhaltet eine Kopie des Zustands des Subjects und besitzt die Fähigkeit, die Kopie mit dem Zustand des Subjects zu synchronisieren. Zusammenarbeit • Ein konkretes Subject benachrichtigt die Observer wenn eine Zustandsänderung sattfindet. • Wenn ein konkreter Observer benachrichtigt wird, dann kann er vom Subject die neue Zustandsinformationen erfragen. aConcreteSubject aConcreteObserver anotherConcreteObserver setState() notify update() getState() update() getState() Abbildung 4.6: Sequenzdiagramm für das Observer Entwurfsmuster Konsequenzen Das Entwurfsmuster Observer erlaubt es die Subjects und die Observer unabhängig voneinander zu variieren. Man kann Subjects wiederverwenden, ohne die Observer wiederzuverwenden und anders herum. Man kann neue Observer hinzufügen, ohne die Subjects oder andere Observer ändern zu müssen. • Abstrakte Koppelung zwischen Subjects und Observern. • Unterstützung von Multicast-Kommunikation. • Unerwartete Aktualisierungen können auftreten wenn Abhängigkeiten zwischen Observern bestehen. 4.4. OBSERVER 63 Implementation • Subjects können im einfachsten Fall einfach Referenzen für die abhängigen Observer speichern. Dieser Ansatz ist nicht sehr effizient wenn es sehr viele Subjects und relativ wenige Observer gibt. Alternativ kann man die Abbildung durch eine assoziative Hashtabelle realisieren, wodurch der Platzbedarf sinkt und dafür die Kosten für die Zugriffe steigen. • Gelegentlich ist es sinnvoll, dass ein Observer von mehreren Subjects abhängig sein kann. In solchen Fällen muss die Operation update das Subject als Parameter übergeben. • Es gibt zwei Optionen für die Frage, wer die Benachrichtigungen auslöst: 1. Sie Operation, die den Zustand verändert, benachrichtigt nach der Zustandsänderung alle Observer. Dieser Ansatz hat den Vorteil, dass die Benutzer eines Subjects sich nicht um die Propagation von Zustandsänderungen kümmern müssen. Der Nachteil dieses Ansatzes ist, das es unter Umständen zu sehr vielen kleinen Aktualisierungen kommen kann. 2. Alternativ kann man die Benutzer eines Subjekt verantwortlich machen, zu geeigneten Zeitpunkten die Benachrichtigung einzuleiten. Damit können Benutzer des Subjects mehrere Zustandsänderungen machen, die durch nur eine Aktualisierung propagiert werden. Nachteil ist allerdings, dass nun die Benutzer eines Subjects für die Propagation verantwortlich sind. • Beim Löschen von Subjects kann es zu ungültigen Referenzen in den abhängigen Observern kommen. Eine Lösungsmöglichkeit ist die Observer über das bevorstehende Löschen eines Subjects zu benachrichtigen. • Es ist sicherzustellen, dass ein Subject in einem konsistenten Zustand ist bevor er die Observer benachrichtigt. • Bei der Benachrichtigung können optional Informationen übergeben werden. Im einen Extremfall wird keine Information übergeben und die Observer müssen alle Informationen erfrage (pull model). Im anderen Extremfall werden alle Informationen übergeben, egal ob die Observer sie benötigen (push model). • Die Effizienz der Benachrichtigungen kann verbessert werden, indem Observer beim Subject registrieren, an welchen Benachrichtigungen sie interessiert sind. • Wenn die Abhängigkeiten zwischen Subjects und Observern sehr komplex sind, dann kann man ein spezielles Objekt einführen, das diese Abhängigkeiten verwaltet und eine spezielle Strategie zur Benachrichtigung der Observer realisiert. Beispiel Abbildung 4.7 zeigt die Realisierung des Entwurfsmusters Observer in den Klassenbibliothek von Java. Das Java Interface Observer definiert eine abstrakte Observer-Schnittstelle. Subjects werden in Java durch die Klasse Observable definiert. Konkrete Subjects müssen von der Klasse Observable abgeleitet werden. 64 KAPITEL 4. ENTWURFSMUSTER Object * Observable +Observable() +addObserver(o:Observer): void +deleteObserver(o:Observer): void +countObservers(): int +notifyObservers(): void +notifyObservers(arg:Object): void +hasChanged(): boolean #setChanged(): void #clearChanged(): void observes «interface» * Observer +update(o:Observable,arg:Object): void HexNumberObserver ObservableNumber -number: int +setNumber(value:int): void +getNumber(): int +update(o:Observable,arg:Object): void DecimalNumberObserver +update(o:Observable,arg:Object): void Abbildung 4.7: Realisierung des Observer Entwurfsmusters in Java Anwendungen Das Entwurfsmuster Observer ist Teil des Model/View/Controller Entwurfsmusters, das innerhalb der Smalltalk-Umgebung für graphische Oberflächen verwendet wird. Das Model entspricht einem Subject und es kann mehrere Views geben, die das Model darstellen können. Die Views entsprechen also den Observern. Viele andere Klassenbibliotheken für graphische Oberflächen benutzen seit dem ebenfalls das Entwurfsmuster Observer. Verwandte Entwurfsmuster Command, Iterator 65 4.5. STRATEGY 4.5 Strategy Kapselt eine Menge von Algorithmen für ein konkretes Problem und macht sie austauschbar. Strategy erlaubt es die Algorithmen zu ändern oder zu erweitern, ohne die Programme zu ändern, die sie benutzen. Synonyme Policy Motivation Oftmals ist es notwendig, eine Aufgabe in verschiedenen Arten zu lösen. Typische Beispiele sind das Speichern von Daten in unterschiedlichen Dateiformaten, die Komprimierung von Daten mit unterschiedlichen Komprimierungsverfahren oder die Darstellung von Daten in unterschiedlichen Diagrammarten. Anwendbarkeit • Wenn viele verwandte Klassen sich nur im Verhalten unterscheiden. • Wenn man verschiedene Varianten eines Algorithmus benötigt, z.B. Algorithmen mit verschiedenem Zeit- und Speicherverhalten. • Wenn Algorithmen Daten benötigen, die gekapselt werden sollen. • Wenn eine Klasse verschiedene Verhaltensweisen implementiert und dies zu vielen bedingten Anweisungen in der Implementierung der Operationen führt. Struktur «interface» Strategy Context +AlgorithmInterface() +ContextInterface() ConcreteStrategyA ConcreteStrategyA ConcreteStrategyA +AlgorithmInterface() +AlgorithmInterface() +AlgorithmInterface() Abbildung 4.8: Klassendiagramm für das Strategy Entwurfsmuster 66 KAPITEL 4. ENTWURFSMUSTER 4.6 Chain of Responsibility Das Chain of Responsibility Entwurfsmuster schafft eine lose Kopplung von Sender und Empfänger einer Nachricht. Mehrere Objekte bekommen die Möglichkeit, einen Auftrag auszuführen. Potentielle Empfänger werden aufgereiht und bekommen nacheinander die Möglichkeit, einen Auftrag zu erledigen. Motivation Oftmals ist es sinnvoll, die Ausführung einer Aktion einem von mehreren möglichen Realisierungen zu überlassen. Beispielsweise kann beim Öffnen einer Datei der Dateiname einer Reihe von Programmen präsentiert werden, bis man ein Programm gefunden hat, das den Dateinamen akzeptiert und öffnen kann. Anwendbarkeit • Wenn es mehrere potentielle Bearbeiter gibt, die einen gegebenen Auftrag potentiell ausführen können und es unklar ist, welcher Bearbeiter der geeignete ist. • Wenn die Menge der Bearbeiter dynamisch verändert werden können soll. Struktur successor «interface» Handler +HandleRequest() ConcreteHandler1 +HandleRequest() ConcreteHandler2 +HandleRequest() Abbildung 4.9: Klassendiagramm für das Chain of Responsibility Entwurfsmuster 4.7. MODEL VIEW CONTROLLER 67 4.7 Model View Controller Das Model View Controller Entwurfsmuster erlaubt die Darstellung von Daten (Model) in mehreren Views, wobei die Integrität der Darstellungen durch das Observer Entwurfsmuster gewährleistet wird. Ein Controller übernimmt die Interaktion mit dem Benutzer, wobei potentiell Änderungen am Model erzeugt werden. Contoller sind in der Regel austauschbar und werden durch das Strategy Entwurfsmuster realisiert. 68 KAPITEL 4. ENTWURFSMUSTER 4.8 Anti Pattern: Creeping Featuritis • Problem: You want to satisfy your customers. You want to do the best possible job you can with your product, and thus win fame and fortune for yourself. • Forces: Programmers have big egos. Customers don’t always know what they want. Programmers often miss the distinction between what’s needed and what’s neat”. • Anti-Solution: You get all of the programmers and designers together in a big room (aka DesignByCommittee) and everybody adds into the product what they want. This process feeds off itself and everyone starts adding new features as the process continues. • Discussion: CreepingFeaturitis can begin, or continue at any stage in the software development process. It’s most common in the Analysis stage, where it results in either an unending Analysis stage (see AnalysisParalysis) or in an unrealistically ambitious specification. In the design phase CreepingFeaturitis is characterized by adding more bells and whistles than were called for in the Analysis, or by trying to abstract everything before anything is ever made concrete. In the coding stage, it is characterized by coding that never ends as programmers continue to add öne more feature”. 4.9 Anti Pattern: Design By Committee • Problem: Given a political environment in which no one person has enough clout to present a design for a system and get it approved, how do you get a design done? • Forces: Often, a problem can be clearly identified for which no existing solution fits well. For instance, the DOD figured out in the mid-late ’70s that the existing programming languages that were being used for big military projects just didn’t cut it. FORTRAN, JOVIAL and COBOL were not going to allow programmers to write the programs necessary to build things like SDI – they didn’t provide large-scale programming support, encapsulation, or a host of other things that language designers had decided that were needed. However, no one had the force of personality or knowledge to drive through a single, consistent solution. There was no Alan Kay or Grace Hopper to provide a vision. So they: • Solution: Put together a big committee to solve the problem. Let them battle it out amongst themselves and finally take whatever comes out the end. • Discussion: The problem with DesignByCommittee is that everyone on the committee has their own vision of the final product. They each fight to get their $0.02 added in to the final version. Since there is no unifying vision, what results is a mish-mash of features in which everybody gets their share put in. By the way, this story relates how Ada came about. Kapitel 5 Qualitätssicherung Mit Hilfe von Maßnahmen zur Qualitätssicherung soll eine bestimmte Softwarequalität erreicht und garantiert werden können. Generell ist das Aufdecken von Fehlern ein destruktiver Prozess und sollte daher auch so organisiert werden, dass nicht die Entwickler selbst für die Qualitätssicherung zuständig sind. Außerdem ist darauf zu achten, dass bei der Qualitätssicherung das Produkt im Vordergrund steht und nicht etwa die Qualität der Entwickler. 5.1 Typische Softwarefehler • Berechnungsfehler: Eine Softwarekomponente berechnet eine falsche Funktion (z.B. durch Verwendung falscher Variablen, Konstanten oder Operatoren). • Schnittstellenfehler: Syntaktische oder semantische Inkonsistenz zwischen Aufruf und Definition einer Schnittstelle. • Kontrollflussfehler: Ausführung eines falschen Programmpfads (z.B. Vertauschung von Anweisungen, falsche Kontrollbedingungen). Sehr beliebt sind off by one“ Fehler, bei der eine ” Schleife einmal zu oft oder einmal zu wenig durchlaufen wird. • Initialisierungsfehler: Falsche oder fehlende Initialisierungen. • Datenflussfehler: Falsche Zugriffe auf Variablen und Datenstrukturen (z.B. falsche Indizierung von Arrays, Zuweisung an falsche Variablen, Zeigerfehler, Speicherfehler). 5.2 Programminspektionen Eine Programminspektion ist ein formales Verfahren, bei dem der Quelltext eines Programms durch andere Personen auf Probleme untersucht wird. Dabei wird folgende Vorgehensweise verfolgt: • Eine Inspektion wird vom Autor ausgelöst, typischerweise bei einer Freigabe für eine weitere Entwicklungsaktivität. • Das Programm wird von mehreren Gutachtern beurteilt, wobei jeder Gutachter sich auf einen oder mehrere Aspekte konzentriert und seine Erkenntnisse dokumentiert. 69 70 KAPITEL 5. QUALITÄTSSICHERUNG • In einer gemeinsamen Sitzung aller Gutachter mit einem ausgebildeten Moderator werden gefundene und neu entdeckte Fehler protokolliert. Lösungen werden nicht diskutiert. • Ergebnis ist ein formalisiertes Inspektionsprotokoll mit Fehlerklassifizierung. Außerdem werden Statistiken über die Fehlerarten und -häufigkeiten erstellt, die zur Verbesserung des Entwicklungsprozesses dienen. • Der Autor überarbeitet sein Produkt anhand der Erkenntnisse. • Der Moderator gibt das Produkt frei oder weist es zurück. Der Inspektionsaufwand muss natürlich im Projektplan eingeplant sein. Generell sind Inspektionsergebnisse sensitives Datenmaterial. Insbesondere dürfen sie nicht zur Beurteilung von Mitarbeitern verwendet werden und Vorgesetzte und Zuhörer dürfen nicht an den Inspektionen teilnehmen. Nach empirischen Untersuchungen liegt der Prüfaufwand bei 15-20% des Entwicklungsaufwands, wobei ca. 60-70% der Fehler in einem Dokument gefunden werden. Der Nettonutzen durch die frühe Fehlererkennung liegt bei 20% in der Entwicklung und 30% in der Wartung. Ein Review ist eine Variante der Programminspektion, bei der kein fester Ablauf vorgeschrieben ist und auch nur ein informelles Prüfprotokoll angefertigt wird. Ein Review ist fast so effektiv wie eine Programminspektion. Die Akzeptanz ist in der Regel höher, da keine sensitiven formalen Inspektionsprotokolle erzeugt werden. Eine Optimierung des Entwicklungsprozesses ist allerdings durch die fehlende Erfassung von Daten nicht möglich. Ein Walkthrough ist eine weiter abgeschwächte Form, in der der Autor das Prüfobjekt Schritt für Schritt vorstellt und die Gutachter spontane Fragen stellen. 5.3 Testverfahren Testverfahren können nach der Transparenz des Prüflings klassifiziert werden: • Black Box: Interne Struktur der Software-Komponente ist unbekannt. • White Box: Interne Struktur der Software-Komponente bildet Testgrundlage. • Grey Box: Zwischenkategorie Zur Veranschaulichung verschiedener Testverfahren betrachten wir im Folgenden eine Java-Klasse Vowel mit der Methode countVowels(): public class Vowel { private char[] s; public Vowel(String sentence) { s = sentence.toCharArray(); } /** Counts how many vowels occur in a sentence. 5.3. TESTVERFAHREN * */ 71 A sentence must be terminated by a dot. public int countVowels() { int i, count; i = 0; count = 0; while (s[i] != ’.’) { if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’ || s[i] == ’o’ || s[i] == ’u’) { count++; } i++; } return count; } } 5.3.1 Funktionale Testverfahren Funktionale Testverfahren testen gegen die Spezifikation und lassen die interne Programmstruktur unberücksichtigt. • Funktionale Testverfahren setzen eine vollständige und widerspruchsfreie Spezifikation voraus und sie benötigen ein Orakel, das die richtigen Testergebnisse aufgrund der Spezifikation ermittelt. • Konstruktion von Testfällen durch Äquivalenzklassenbildung: – Wertebereiche von Ein- und Ausgaben werden in Äquivalenzklassen eingeteilt. – Bildung der Äquivalenzklassen orientiert sich ausschließlich an der Spezifikation. – Äquivalenzklassen für gültige und ungültige Werte. – Getestet wird jeweils nur noch für einen Repräsentanten einer Äquivalenzklasse. • Regeln zur Äquivalenzklassenbildung: – Jeder spezifizierte Eingabebereich induziert mindestens eine gültige und eine ungültige Klasse. – Jede Eingabebedingung induziert eine gültige (Bedingung erfüllt) und eine ungültige (Bedingung nicht erfüllt) Klasse. – Bildet eine Eingabebedingung eine Menge von Werten, die unterschiedlich behandelt werden, ist für jeden Fall eine gültige und eine ungültige Äquivalenzklasse zu bilden. – Für Ausgaben werden analog Äquivalenzklassen gebildet. • Wahl der Repräsentanten: – Zufällige Auswahl – Wahl der Repräsentanten an den Rändern der Klassen, da Grenzbereiche besonders häufig fehlerhaft sind. 72 KAPITEL 5. QUALITÄTSSICHERUNG Betrachten wir die Konstruktion von Äquivalenzklassen für die Methode countVowels(). Als Spezifikation liegt lediglich der Kommentar vor, aus dem sich aber schon drei Äquivalenzklassen ableiten lassen: - K1: Die Zeichenfolge endet nicht mit einem Punkt. - K2: Die Zeichenfolge endet in einem Punkt und enthält keine Vokale. - K3: Die Zeichenfolge endet in einem Punkt und enthält Vokale. Da countVowels() Vokalen unterscheiden können muss, sollte die Klasse K3 noch weiter unterteilt werden: - K3a-K3e: Die Zeichenfolge enthält ein a, e, i, o, u. - K3f-K3j: Die Zeichenfolge enthält ein A, E, I, O, U. Entsprechend der Ausgabe können noch zwei weitere Klassen unterschieden werden: - K4: Die Zeichenfolge enthält mehrere gleiche Vokale. - K5: Die Zeichenfolge enthält mehrere unterschiedliche Vokale. Man kann drei Repräsentanten auswählen, die diese Äquivalenzklassen abdecken. Damit erhält man z.B. folgenden Test: import junit.framework.TestCase; import junit.framework.Assert; public class VowelTest extends TestCase { Vowel v1, v2, v3; public VowelTest(String name) { super(name); } protected void setUp() { v1 = new Vowel("X"); v2 = new Vowel("."); v3 = new Vowel("XAaEeIiOoUuA."); } public void testClass1() { Assert.assertEquals(0, v1.countVowels()); } public void testClass2() 5.3. TESTVERFAHREN 73 { Assert.assertEquals(0, v2.countVowels()); } public void testClass3() { Assert.assertEquals(11, v3.countVowels()); } } Offensichtlich besitzt countVowels() Fehler und besteht diesen funktionalen Test nicht. Man beachte aber auch, dass die Spezifikation von countVowels() unvollständig ist. 5.3.2 Kontrollfluss-orientierte Testverfahren Kontrollflussorientierte Testverfahren betrachten bestimmte Strukturelemente eines Programms wie z.B. Anweisungen, Zweige, Bedingungen oder Pfade und fordern von den Testfällen, dass der Kontrollfluss jedes dieser Elemente mindestens einmal erreicht (Überdeckungstest). Programmdarstellung als Kontrollflussgraph: • Knoten stellen Anweisungen oder Bedingungen einer Kontrollstruktur dar. • Kanten stellen den möglichen Kontrollfluss zwischen zwei Anweisungen dar. • Ein Pfad ist eine Kombination von Kanten, die vom Startknoten zum Endknoten führt. Die Methode countVowels() besitzt den in Abbildung 5.1 dargestellten Kontrollflussgraphen. Der wesentliche Wert kontrollfluss-orientierter Verfahren liegt darin, dass die Qualität der Testfallsammlung beurteilt werden kann. Kontrollfluss-orientierte Testverfahren werden eher selten zur Konstruktion von Testfällen benutzt. Anweisungsüberdeckung • Jeder Knoten des Kontrollflussgraphen muss einmal ausgeführt werden. • Nicht sehr leistungsfähiges Minimalkriterium. • Unentdeckte Fehler in nicht ausgeführten Zweigen. Zweigüberdeckung • Jede Kante des Kontrollflussgraphen muss einmal durchlaufen werden. • Realistisches Minimalkriterium, das die Anweisungsüberdeckung einschließt. • Unentdeckte Fehler bei Kombinationen und Wiederholungen von Schleifen. 74 KAPITEL 5. QUALITÄTSSICHERUNG i=0 count = 0 while (s[i] != ’.’) if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’ || s[i] == ’o’ || s[i] == ’u’) count++ i++ Abbildung 5.1: Kontrollflussgraph für countVowels() Bedingungsüberdeckung • Jede Bedingung muss mindestens einmal den Wert false und mindestens einmal den Wert true annehmen. • Bei atomarer Bedingungsüberdeckung muss jede einfache Bedingung den Wert true oder false annehmen. Die atomare Bedingungsüberdeckung umfasst nicht die Anweisungsüberdeckung. • Bei der minimalen Mehrfachbedingungsüberdeckung muss jede Bedingung (ob einfach oder nicht) einmal den Wert false und einmal den Wert true liefern. • Die minimale Mehrfachbedingungsüberdeckung orientiert sich an der syntaktischen Struktur von Bedingungen und ist ein guter Kompromiss und impliziert die Zweigüberdeckung bei einer Anweisungsüberdeckung. Pfadüberdeckung • Jeder Pfad des Kontrollflussgraphen muss einmal durchlaufen werden. • Die Pfadüberdeckung ist ein theoretisches Kriterium, da in den meisten praktischen Fällen durch Schleifen unendlich viele Pfade möglich sind. Die Pfadüberdeckung dient als Vergleichsmaßstab für andere Überdeckungstests. • Selbst eine Pfadüberdeckung findet nicht alle Fehler. 5.3. TESTVERFAHREN 5.3.3 75 Mutationstesten Beim Mutationstesten werden kleine Änderungen (Mutationen) im Quelltext vorgenommen und es wird anschließend überprüft, wie viele der so erzeugten Mutanten von den Tests erkannt werden. Auch hier geht es wiederum um die Frage, welche Qualität eine Testfallsammlung besitzt. • Im Idealfall sollte ein Satz von Testfällen alle Mutanten erkennen. • Mutanten kann man automatisch erzeugen und so auch automatisch die Qualität der Testfälle ermitteln. • Es werden nur einfache“ Fehler erzeugt unter der Annahme, dass sich komplexe Fehler aus ” einfachen zusammensetzen. Man unterscheidet folgende Mutationsoperatoren: • Berechnungsfehler: – Ändern von arithmetischen Operationen – Löschen von arithmetischen (Teil-) Ausdrücken – Ändern von Konstanten • Schnittstellenfehler: – Vertauschen / Ändern von Parametern – Aufruf anderer Methoden • Kontrollflussfehler: – Ersetzen von logischen (Teil-) Ausdrücken durch true und false – Ändern von logischen und relationalen Operatoren – Aufruf anderer Methoden – Löschen von Anweisungen – Einfügen von HALT-Anweisungen • Initialisierungsfehler: – Änderungen von Konstanten – Löschen von Initialisierungsanweisungen • Datenflussfehler: – Vertauschen von Variablen in einem Sichtbarkeitsbereich – Änderungen in der Indexberechnung Wird eine Mutation nicht von den Testfällen erkannt, so ist zu überprüfen, inwieweit die Mutation nicht die Korrektheit des Programms beeinflusst hat. (Manche Mutationen wie z.B. die Vertauschung von Anweisungen können durchaus zu einem weiterhin korrekten Programm führen.) 76 KAPITEL 5. QUALITÄTSSICHERUNG 5.3.4 Regressionstesten Beim Regressionstesten geht es darum, nach Änderungen am Programmtext sicherzustellen, dass die bisher bestandenen Tests auch von der neuen, veränderten Programmversion bestanden werden. Normalerweise wächst die Sammlung an Tests im Laufe der Zeit, da bei jeder Fehlerbehebung zunächst ein Testfall geschrieben werden sollte, der den Test auslöst. Das konsequente Regressionstesten hat somit zur Folge, dass tendenziell die Qualität der Software im Laufe der Zeit ebenfalls besser werden sollte. 5.4 Softwaremetriken Mit Hilfe von speziellen Metriken kann man versuchen, die Eigenschaften einer Software durch die Analyse des Quelltextes zu ermitteln. Durch den Vergleich der gemessenen Metriken mit Werten aus anderen Projekten und Standards einer ganzen Organisation kann man versuchen, die Qualitätseigenschaften der Software und des Entwicklungsprozesses zu messen. Das grundlegende Problem hierbei ist die Tatsache, dass die eigentlich interessanten Größen wie z.B. Wartbarkeit, Zuverlässigkeit, Portierbarkeit und Benutzerfreundlichkeit nicht messbar sind. Stattdessen werden andere leichter berechenbare Metriken als Indikatoren für diese Größen benutzt, was natürlich immer reichlich Spielraum zur Interpretation lässt. Man kann Metriken in zwei Klassen einteilen: • Statische Metriken werden durch Messungen der Artefakte (Entwurfsdiagramme, Quelltexte, Dokumentation) ermittelt. • Dynamische Metriken werden durch Messungen bei der Ausführung von Programmen ermittelt. Gebräuchliche statische Softwaremetriken: • Fan-in / Fan-out: Die Anzahl der Funktionen, die eine gegebene Funktion aufrufen (fan-in) bzw. die Anzahl der Funktionen, die von einer gegebenen Funktion aufgerufen werden (fanout). Ein hoher Fan-in deutet auf eine enge Verknüpfung der betrachteten Funktion hin und entsprechend können Änderungen an der Funktion tiefgreifende Auswirkungen haben. Ein hoher Fan-out deutet darauf hin, dass die betrachtete Funktion eine komplexe Steuerlogik besitzt. • Größe des Quellcodes: Generell sind in einem großen Programm mehr Fehler zu erwarten als in einem kleinen Programm. Obwohl die Größe des Quellcodes eine sehr einfache und unscharfe Metrik ist, wird sie in der Praxis gerne eingesetzt. • Länge der Bezeichner: Bei dieser Metrik wird die durchschnittliche Länge von Bezeichnern ermittelt. Ein Programm mit sehr kurzen Bezeichnern ist in der Regel schwer verständlich. Dasselbe gilt allerdings auch für Programme mit zu langen Bezeichnern. • Tiefe der Verschachtelungen und Verzweigungen: Tief verschachtelte bedingte Anweisungen sind in der Regel schwer verständlich. 77 5.4. SOFTWAREMETRIKEN • Fog-Index: Ein Maß für die durchschnittliche Länge von Wörtern und Sätzen in Dokumenten. Ein Dokument mit sehr langen Sätzen kann schwerer zu verstehen sein. • Zyklomatische Komplexität: Die zyklomatische Komplexität wurde von McCabe eingeführt und ist auf der Basis eines Kontrollflussgraphen definiert. Die zyklomatische Zahl V (G) eines Kontrollflussgraphen G mit e Kanten, n Knoten und p Verbindungen zu anderen Komponenten ist gegeben durch: V (G) = e − n + 2p Im Normalfall ist p = 1. Für den Kontrollflussgraphen aus Abbildung 5.1 ergibt sich mit e = 9, n = 8 und p = 1 die zyklomatische Komplexität V (G) = 9 − 8 + 2 · 1 = 3. Die zyklomatische Komplexität ist relativ einfach zu berechnen und besser als die Anzahl der Quelltextzeilen. Allerdings werden viele Programmmerkmale stark vereinfacht (z.B. Komplexität von Bedingungen) und es gibt entsprechend viele Vorschläge für Verbesserungen der zyklomatischen Komplexität. • Halstead Metriken: Grundlage der Halstead-Metriken sind die in einem Programm benutzten unterschiedlichen Operanden und Operatoren. Ein Operator in diesem Zusammenhang ist jedes Symbol oder Schlüsselwort, das eine Aktion kennzeichnet. Operanden sind alle Symbole, die Daten darstellen (z.B. Variable, Konstante, Sprungmarken). Bestimmt werden die folgenden Basisgrößen: η1 η2 N1 N2 : : : : Anzahl der unterschiedlichen Operatoren Anzahl der unterschiedlichen Operanden Gesamtzahl der verwendeten Operatoren Gesamtzahl der verwendeten Operanden Die Größe des Vokabulars ist gegeben durch η = η1 + η2 und die Länge der Implementierung durch N = N1 + N2 . Ein Maß für die Schwierigkeit ein Programm zu schreiben oder zu verstehen ist die Metik D, die wie folgt definiert ist: D= η1 · N2 2η2 Nach Halstead beschreibt D den Aufwand zum Schreiben von Programmen, den Aufwand bei Reviews und das Verstehen bei Wartungsvorgängen. Insgesamt sind Halstead-Metriken einfach zu berechnen und ein brauchbares Maß für Komplexität. Allerdings sind die Klassifikationsregeln für Operanden und Operatoren nicht unbedingt eindeutig und die HalsteadMetriken berücksichtigen nur lexikalische Komplexität und ignorieren Namensräume in moderneren Programmiersprachen. • Tiefe und Breite des Vererbungsgraphen: Gemessen wird die Struktur des Vererbungsgraphen. Sehr tiefe Vererbungsgraphen sind in der Regel schwer zu überschauen und zu warten. • Gewichtete Anzahl der Methoden pro Klasse: Bestimmung der Anzahl Methoden pro Klasse, wobei die Methoden gewichtet werden. Dabei kann die Komplexität einer Methode in die Gewichtung einfließen. • Anzahl überschreibender Methoden: Bestimmung, wie oft eine Methode überschrieben wird. Wird eine Methode sehr häufig überschrieben, so kann es sein, dass die realisierte Semantik unpassend gewählt wurde. 78 KAPITEL 5. QUALITÄTSSICHERUNG Allgemein ist bei der Verwendung von Metriken zu berücksichtigen, dass Programmierer bei Kenntnis der verwendeten Metriken den Quelltext so organisieren können, dass ein beabsichtigter Effekt eintritt. Ein etwas anderer Ansatz geht davon aus, dass Entwickler sehr wohl relativ schnell schlechten Quelltext erkennen können, auch wenn Sie oftmals nicht spontan erklären können, warum der Quelltext schlecht ist (bad smelling code). Einige typische Indikatoren für schlecht riechenden“ ” Quelltext: • Duplizierter Quelltext • Zu lange und komplexe Methoden • Klassen mit sehr vielen Instanzvariablen • Klassen mit zu wenigen Instanzvariablen • Klassen mit zu viel Quelltext • Klassen mit zu wenig Quelltext • Viele sehr ähnliche Unterklassen • Leere catch-Anweisungen • Mehrfachbenutzung von Variablen • ... Die graphische Darstellung mehrerer Metriken erfolgt oftmals unter Verwendung von Kiviat-Graphen. 5.5 Qualitätssicherung mit ISO 9000 Das ISO 9000 Normenwerk legt für das Auftraggeber-Lieferantenverhältnis einen allgemeinen, organisatorischen Rahmen zur Qualitätssicherung fest. Literaturhinweise Informationen zur Qualitätssicherung findet man bei Balzert [1] und Sommerville [25]. Ein mittlerweile klassisches Buch zum Thema Softwaretests stammt von Myers [15]. Kapitel 6 Werkzeuge Zur effizienten Softwareentwicklung ist eine Werkzeugunterstützung notwendig. Im folgenden werden einige ausgewählte Werkzeuge kurz vorgestellt, die insbesondere in den späten Phasen eines Softwareprojekts sinnvoll eingesetzt werden können. Weitere Informationen findet man in [29]. 6.1 Debugger Zur Fehlersuche ist es gelegentlich nützlich, das Programmverhalten an kritischen Stellen genau inspizieren zu können. Debugger sind Werkzeuge, mit denen ich Programme in Einzelschritten ablaufen lassen kann und den jeweils aktuellen Zustand inspizieren und ggf. auch verändern kann. Voraussetzung ist oftmals, dass bei der Übersetzung spezielle Debug-Informationen erzeugt wurden. 6.1.1 Java Debugger (jdb) Die Standardumgebung von Java enthält den Debugger jdb. Mit jdb kann ein Java-Programm beliebig angehalten, in Einzelschritten ausgeführt und der Inhalt von Variablen inspiziert und auch während der Ausführung verändert werden. Der jdb besitzt eine einfache und sehr minimale Schnittstelle. $ jdb junit.textui.TestRunner VowelTest Initializing jdb... 0x405df8f0:class(junit.textui.TestRunner) > stop in Vowel.countVowels Breakpoint set in Vowel.countVowels > run run junit.textui.TestRunner VowelTest running ... main[1] . Breakpoint hit: Vowel.countVowels (Vowel:15) main[1] list 11 */ 12 13 public int countVowels() { 14 int i, count; 79 80 KAPITEL 6. WERKZEUGE 15 => 16 17 18 19 main[1] next i = 0; count = 0; while (s[i] != ’.’) { if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’ || s[i] == ’o’ || s[i] == ’u’) { Breakpoint hit: Vowel.countVowels (Vowel:16) main[1] main[1] list 12 13 public int countVowels() { 14 int i, count; 15 i = 0; 16 => count = 0; 17 while (s[i] != ’.’) { 18 if (s[i] == ’a’ || s[i] == ’e’ || s[i] == ’i’ 19 || s[i] == ’o’ || s[i] == ’u’) { 20 count++; main[1] next Breakpoint hit: Vowel.countVowels (Vowel:17) main[1] main[1] locals Method arguments: this = Vowel@80bc241 Local variables: i = 0 count = 0 6.1.2 Data Display Debugger (ddd) Etwas mehr Komfort liefern graphische Debugger, die zum Teil nur eine graphische Benutzungsoberfläche auf einen existierenden Debugger wie z.B. jdb oder gdb aufsetzen. Ein gutes Beispiel ist der Data Display Debugger ddd, der insbesondere auch in der Lage ist, komplexere Datenstrukturen zu visualisieren. 6.2 Programmkonstruktion Bei der Implementierung ist es oftmals erforderlich, komplexere Artefakte (Software, Dokumentation) zu erstellen, die sich aus einzelnen Komponenten zusammensetzen. Zwischen den einzelnen Komponenten bestehen in der Regel Abhängigkeiten, so daß die Änderung an einem Dokument Änderungen an daraus erzeugten Dokumenten erfordert. Werkzeuge zur Programmkonstruktion beschreiben die Abhängigkeiten und unterstützen die automatische Aktualisierung von Dokumenten. 6.2.1 make Die Abhängigkeiten zwischen Komponenten eines Systems können durch einen Abhängigkeitsgraphen dargestellt werden. Ein Makefile ist eine textuelle Beschreibung des Abhängigkeitsgraphen nebst Kommandos zur Erzeugung der einzelnen Komponenten. Beim Aufruf von make wird ein interner Abhängigkeitsgraph konstruiert und es wird geprüft, ob es abhängige Dateien gibt, die älter sind als die Dateien, von denen sie abhängen. Ist das der Fall, so wird die abhängige Datei durch Ausführung des zugeordneten Kommandos aktualisiert. 6.2. PROGRAMMKONSTRUKTION Abbildung 6.1: Data Display Debugger CLASSPATH = .:/usr/share/java/junit.jar JAVA = JAVAC = JAVADOC = JFLAGS = java -classpath $(CLASSPATH) javac -classpath $(CLASSPATH) javadoc -classpath $(CLASSPATH) -g SOURCES = CLASSES = Vowel.java VowelTest.java $(SOURCES:.java=.class) %.class: %.java $(JAVAC) $(JFLAGS) $< all: $(CLASSES) test: $(CLASSES) 81 82 KAPITEL 6. WERKZEUGE $(JAVA) junit.textui.TestRunner VowelTest test-awt: $(CLASSES) $(JAVA) $(JFLAGS) junit.awtui.TestRunner VowelTest doc: rm -rf $@; mkdir $@ $(JAVADOC) -d $@ $(SOURCES) clean: rm -f $(CLASSES) rm -rf doc VowelTest.class: VowelTest.java Vowel.java Vowel.class: Vowel.java 6.3 Versionsmanagement Bei der Entwicklung von Software fallen viele verschiedene Versionen der einzelnen Dokumente an. Außerdem ist es nicht unüblich, dass mehrere Entwickler gleichzeitig an einem Dokument arbeiten. Ein Werkzeug zur Versionskontrolle verwaltet die verschiedenen Versionen und erlaubt es, jederzeit eine ganz bestimmte Menge von bestimmten Versionen zu erhalten. 6.3.1 Revision Control System (RCS) Das Revision Control System (RCS) verwaltet Revisionen und Varianten. • Revisionen sind zeitlich aufeinanderfolgende Versionen eines Dokuments. • Varianten sind gleichzeitig existierende alternative Ausprägungen eines Dokuments • Die Ordnung der Versionen wird durch einen Versionsgraphen dargestellt: 2.0.1.1.1 2.0.1.1 2.0.1.2 2.0.1.3 2.0.1.4 1.0 1.1 2.0 2.1 2.2 2.3 Abbildung 6.2: RCS Versionsgraph eines Dokuments • RCS speichert alle Versionen eines Dokuments in einer Datei. • Alle Versionen sind jederzeit rekonstruierbar. • Um den Platzbedarf für eine potentiell große Zahl von Versionen gering zu halten, speichert RCS zu jeder Version nur die Unterschiede zum Vorgänger. Allein die letzte Version wird vollständig gespeichert. 6.3. VERSIONSMANAGEMENT 83 • Zu jeder Version werde Attribute wie Datum, Uhrzeit, Autor, Beschreibung und die Versionsnummer gespeichert. • Zusätzlich können bestimmte Versionen eines Dokuments markiert werden. • RCS verhindert, dass zwei Entwickler gleichzeitig eine Datei modifizieren (pessimistischer Ansatz). • Die wichtigsten Befehle sind: co ci rcsmerge 6.3.2 Arbeitsdatei (bestimmte Revision) aus der RCS-Datei nehmen. Arbeitsdatei als neue Revision in die RCS-Datei übernehmen. Integration zweier Varianten. Concurrent Versions System (CVS) Das Concurrent Versions System unterstützt im Gegensatz zum RCS die Versionierung ganzer Dateibäume. Insbesondere wird das Hinzufügen und Löschen von Dateien berücksichtigt. Vowel.java Makefile VowelTest.java Abbildung 6.3: CVS Konfigurationen • Ein Schnitt durch die Versionen der verschiedenen Dokumente wird als Konfiguration bezeichnet. • Die einzelnen Versionen eines Dokuments werden via RCS verwaltet. • Konfigurationen können markiert werden. • Mehrere Entwickler können sich lokale Kopien derselben Konfiguration anlegen lassen und darauf unabhängig arbeiten. • Erst beim Einbringen der jeweiligen Änderungen wird geprüft, ob sich die einzelnen Änderungen überlappen (optimistischer Ansatz). • Die wichtigsten Befehle sind: cvs cvs cvs cvs cvs cvs checkout commit add remove update diff Lokale Kopie eines CVS-Moduls anlegen. Änderungen in das CVS-Archiv zurückschreiben. Neue Datei in das CVS-Archiv aufnehmen. Existierende Datei aus dem CVS-Archiv löschen. Kopie mit der aktuellen CVS-Version synchronisieren. Unterschiede relativ zur aktuellen CVS-Version zeigen. 84 KAPITEL 6. WERKZEUGE 6.4 Testwerkzeuge Testwerkzeuge haben die Aufgabe, die automatische Durchführung von Komponententests oder Regressionstests zu unterstützen. 6.4.1 JUnit Hinter JUnit [9, 8] verbirgt sich ein Rahmen für automatisierte Tests von Java Komponenten (in der Regel Java Klassen). Beim Entwurf von JUnit wurde insbesondere darauf geachtet, dass es für Java-Entwickler keine besondere Mühe ist, Tests zu schreiben und zu pflegen. Assert +assertEquals(): void +assertNull(): void +assertNotNull(): void +assertSame(): void +assertTrue(): void +fail(): void TestResult «interface» Test contains +run(result:TestResult): void TestCase +run(result:TestResult): void +setUp(): void +tearDown(): void +runTest(): void +suite(): Test TestSuite +fTests: Vector +run(result:TestResult): void +runTest(): void Abbildung 6.4: UML Klassendiagramm für JUnit Abbildung 6.4 zeigt die grundlegenden Klassen von JUnit: • Die Klasse TestCase beschreibt einen Testfall. Die Implementation von Testfällen erfolgt durch das Ableiten von speziellen Klassen aus der Klasse TestCase. • Die Klasse TestSuite beschreibt eine Menge von Tests, wobei die Elemente einer TestSuite wiederum TestSuites oder TestCases sein können. • Die Klasse Assert stellt eine Reihe von primitiven Vergleichen zur Verfügung, die der Entwickler zur Konstruktion seiner Testfälle benutzen kann. • Die Klasse TestResult protokolliert den Testablauf. • Die Operation setUp stellt eine spezielle Testumgebung für einen Testfall bereit (fixture). • Die Operation tearDown löscht die Testumgebung. • Die Operation suite dient als Einstiegspunkt zur Abarbeitung eines Testfalls. • Die einzelnen Tests eines Testfalls werden als Operationen implementiert, die mit dem Namenspräfix test“ beginnen. ” 6.4. TESTWERKZEUGE 85 Beispiel 1 Betrachten Sie eine Klasse Temperature, die Temperaturen repräsentiert und insbesondere die Umrechnung von Grad Celsius (c) in Grad Fahrenheit (f ) und umgekehrt unterstützt. Es gelten die folgenden Beziehungen: 9 f = c + 32 5 5 c = (f − 32) 9 Eine einfache Implementation der Klasse Temperature in Java könnte folgendermaßen aussehen: // temperature/Temperature.java public class Temperature { private float celsius; public Temperature() { celsius = 0; } public void setCelsius(float degrees) { celsius = degrees; } public float getCelsius() { return celsius; } public void setFahrenheit(float degrees) { celsius = (degrees - 32) * 5 / 9; } public float getFahrenheit() { return (celsius * 9 / 5 + 32); } } Eine Sammlung von grundlegenden Testfällen zur Klasse Temperature in JUnit: // temperature/TemperatureTest.java import import import import junit.framework.Test; junit.framework.TestCase; junit.framework.TestSuite; junit.framework.Assert; public class TemperatureTest extends TestCase { private Temperature zeroCelsius; 86 KAPITEL 6. WERKZEUGE private Temperature zeroFahrenheit; private Temperature hundredCelsius; private Temperature hundredFahrenheit; private static final float precision = (float) 0.0001; public TemperatureTest(String name) { super(name); } protected void setUp() { zeroCelsius = new Temperature(); zeroCelsius.setCelsius(0); zeroFahrenheit = new Temperature(); zeroFahrenheit.setFahrenheit(0); hundredCelsius = new Temperature(); hundredCelsius.setCelsius(100); hundredFahrenheit = new Temperature(); hundredFahrenheit.setFahrenheit(100); } protected void tearDown() { zeroCelsius = null; zeroFahrenheit = null; hundredCelsius = null; hundredFahrenheit = null; } public static Test suite() { return new TestSuite(TemperatureTest.class); } public void testZeroCelsius() { Assert.assertEquals(0, zeroCelsius.getCelsius(), precision); Assert.assertEquals(32, zeroCelsius.getFahrenheit(), precision); } public void testHundredCelsius() { Assert.assertEquals(100, hundredCelsius.getCelsius(), precision); Assert.assertEquals(212, hundredCelsius.getFahrenheit(), precision); } public void testZeroFahrenheit() { Assert.assertEquals(-17.7777, zeroFahrenheit.getCelsius(), precision); Assert.assertEquals(0, zeroFahrenheit.getFahrenheit(), precision); } 6.5. INTEGRIERTE ENTWICKLUNGSUMGEBUNGEN 87 public void testHundredFahrenheit() { Assert.assertEquals(37.7777, hundredFahrenheit.getCelsius(), precision); Assert.assertEquals(100, hundredFahrenheit.getFahrenheit(), precision); } } 6.5 Integrierte Entwicklungsumgebungen Integrierte Entwicklungsumgebungen (integrated development environments, IDE) stellen eine Entwicklungsumgebung bereit, bei der die Funktionen vieler einzelner Werkzeuge integriert sind. Der Vorteil liegt in einer konsistenteren Benutzungsoberfläche. Andererseits sind die einzelnen Werkzeuge in integrierten Entwicklungsumgebungen häufig unterschiedlich ausgereift und lassen sich nur bedingt durch andere Komponenten austauschen. Für Java gibt es unter anderem die folgenden integrierten Entwicklungsumgebungen: • JBuilder (Borland) • NetBeans (Open Source) • Forte4J (Sun Microsystems), basiert auf NetBeans • VisualAge for Java (IBM) • Visual J++ (Microsoft) • JDEE (Open Source), basiert auf Emacs 88 KAPITEL 6. WERKZEUGE Teil II Graphische Benutzungsoberflächen 89 Kapitel 7 Ergonomische Aspekte Ziel der Software-Ergonomie ist die Entwicklung und Evaluierung gebrauchstauglicher Software-Produkte, die Benutzer zur Erreichung ihrer Arbeitsergebnisse befähigen und dabei ihre Belange im jeweiligen Nutzungskontext beachten [1, 11]. Software-Ergonomie ist ein relativ junges Fachgebiet, das starke Berührungspunkte zu anderen Fachgebieten besitzt: • Kognitionspsychologie: Gedächtnis, Verstehen, Lernen, Handeln, Problemlösen • Wahrnehmungspsychologie: visuelle, akustische, taktile Wahrnehmung • Physiologie: Sensorik, Motorik, Körperhaltung • Arbeitspsychologie: Arbeitsorganisation, Schulung, Benutzerpartizipation, Evaluation, Arbeitssicherheit • Arbeitsplatzgestaltung: Möbel, Anordnung, Raumgestaltung, Beleuchtung • Software-Engineering: Analyse, Modellierung, Entwurf, Prototyping, Werkzeuge • Unterstützungssysteme: Hilfesysteme, Individualisierungssysteme, Aktivitätenmanagement, Tutorielle Systeme • Dialogparadigmen: Menüs und Masken, Kommandos, Direkte Manipulation, Hypermedia • Dialogtechniken: Informationsdarstellung, Interaktionsformen, Dialogablauf Eine optimale Anpassung zwischen Softwaresystemen und Benutzern erhält man, wenn Softwaresysteme explizite Benutzermodelle anlegen und die Interaktion mit dem Benutzer entsprechend dieser Modelle steuern. In der Regel unterscheiden die meisten heutigen Softwaresysteme nur Benutzergruppen: • Anfänger (novices, beginners) • Gelegenheitsbenutzer (casual users, infrequent users) • Experten (experts, frequent users, sophisticated users) 7.1 Dialogarten und Dialogmodi • Ein Primärdialog ist ein Dialog, der erst bei der Fertigstellung einer Aufgabe beendet wird (z.B. Bearbeitung eines UML-Diagramms). • Ein Sekundärdialog ist ein kurzzeitiger Dialog, der zur Erledigung einer Aufgabe erforderliche Informationen anzeigt oder erfragt (z.B. Dateiauswahldialog). 91 92 KAPITEL 7. ERGONOMISCHE ASPEKTE • Bei einem modalen Dialog kann die Applikation erst dann weiter benutzt werden, wenn der Dialog beendet worden ist. • Ein nicht-modaler Dialog gestattet es dem Benutzer, den aktuellen Dialog zu Unterbrechen (d.h. andere Funktionen auszuführen) und später den Dialog fortzusetzen. • Generell empfiehlt sich, modale Dialoge möglichst zu vermeiden, da sie die Flexibilität extrem einschränken. Dies gilt insbesondere für modale Dialoge, die auch die Benutzung anderer Applikationen unmöglich machen oder stark behindern. • Bei der funktionsorientierten Bedienung wählt der Benutzer zunächst eine Funktion aus und bestimmt anschließend auf welche Objekte diese Funktion angewendet werden soll (z.B. Kommandosprachen). • Bei der objektorientierten Bedienung wählt der Benutzer zuerst die Objekte aus, die er bearbeiten will, und anschließend die Funktion, die er auf den Objekten ausführen will (z.B. graphische Benutzungsoberflächen mit direkter Manipulation). 7.2 Grundsätze zur Dialoggestaltung Die europäische ISO-Norm 9241-10 [22] definiert allgemeine Anforderungen an die Gestaltung von Dialogen. Die folgende Darstellung ist an [1] angelehnt. 7.2.1 Aufgabenangemessenheit Ein Dialog ist aufgabenangemessen, wenn er den Benutzer unterstützt, seine Arbeitsaufgabe effektiv und effizient zu erledigen. • Es sind keine zusätzlichen technischen Vor- und Nachbereitungen durch den Benutzer erforderlich. • Der Dialog ist an die zu erledigenden Arbeitsaufgaben angepasst. Art, Umfang und Komplexität der vom Benutzer zu verarbeitenden Informationen sind berücksichtigt. • Regelmäßig wiederkehrende Arbeitsaufgaben werden unterstützt, z.B. durch Makrokommandos. • Eingabevorbelegungen sind — soweit sinnvoll möglich — vorzunehmen; sie sind vom Benutzer änderbar. 7.2.2 Selbstbeschreibungsfähigkeit Ein Dialog ist selbstbeschreibungsfähig, wenn jeder einzelne Dialogschritt durch Rückmeldung des Dialogsystems unmittelbar verständlich ist oder dem Benutzer auf Anfrage erklärt wird. • Der Benutzer muss sich zweckmäßige Vorstellungen von den Systemzusammenhängen machen können (Unterstützung beim Aufbau mentaler Modelle). • Erläuterungen sind an allgemein übliche Kenntnisse der zu erwartenden Benutzer angepasst. • Wahl zwischen kurzen und ausführlichen Erläuterungen. • Kontextabhängige Erläuterungen. • Dynamische Einblendung von Kurzbeschreibungen bei der Verwendung von Piktogrammen (tool tips). 7.2. GRUNDSÄTZE ZUR DIALOGGESTALTUNG 7.2.3 93 Steuerbarkeit Ein Dialog ist steuerbar, wenn der Benutzer in der Lage ist, den Dialogablauf zu starten sowie seine Richtung und Geschwindigkeit zu beeinflussen, bis das Ziel erreicht ist. • Bedienung kann der eigenen Arbeitsgeschwindigkeit angepasst werden. • Arbeitsmittel und -wege sind durch den Benutzer frei wählbar. • Vorgehen in leicht durchschaubaren Dialogschritten, wobei mehrere Schritte zusammenfassbar sein sollten. • Der Benutzer erhält Informationen, die für die Arbeitswegplanung benötigt werden (z.B. Anzeige, welche Funktionen als nächstes wählbar sind). • Ein Dialog kann beliebig unterbrochen und wieder aufgenommen werden. • Mehrstufiges undo, d.h. Rücknehmbarkeit zusammenhängender Dialogschritte (Widerruf). • Mehrstufiges redo, d.h. rückgängig gemachte Funktionen wieder ausführen ohne erneute Dateneingabe. • Sicherheitsabfragen bei Aktionen von großer Tragweite. • Steuerung der Menge der angezeigten Informationen. 7.2.4 Erwartungskonformität Ein Dialog ist erwartungskonform, wenn er konsistent ist und den Merkmalen des Benutzers entspricht, z.B. seinen Kenntnissen aus dem Arbeitsgebiet, seiner Ausbildung und seiner Erfahrung sowie den allgemein anerkannten Konventionen. • Das Dialogverhalten ist einheitlich (z.B. konsistente Anordnung von Bedienungselementen). • Bei ähnlichen Arbeitsaufgaben ist der Dialog ähnlich gestaltet (z.B. Standarddialoge zum öffnen von Dateien). • Zustandsänderungen des Systems, die für die Dialogführung relevant sind, werden dem Benutzer mitgeteilt. • Eingaben in Kurzform werden im Klartext bestätigt. • Systemantwortzeiten sind den Erwartungen des Benutzers angepasst, sonst erfolgt eine Meldung. • Die Benutzer sind über den Stand der Bearbeitung informiert. • Die Interaktionselemente zur Steuerung der Applikation funktionieren immer auf die gleiche Art und Weise. 7.2.5 Fehlertoleranz Ein Dialog ist fehlertolerant, wenn das beabsichtigte Arbeitsergebnis trotz erkennbar fehlerhafter Eingaben entweder mit keinem oder mit minimalem Korrekturaufwand seitens des Benutzers erreicht werden kann. • Benutzereingaben dürfen niemals zu Systemabstürzen oder undefinierten Systemzuständen führen. • Automatisch korrigierbare Fehler können korrigiert werden. Der Benutzer ist darüber zu informieren. • Die automatische Korrektur ist abschaltbar. • Korrekturalternativen für Fehler werden dem Benutzer angezeigt. 94 KAPITEL 7. ERGONOMISCHE ASPEKTE • Fehlermeldungen weisen auf den Ort des Fehlers hin, z.B. durch Markierung der Fehlerstelle. • Fehlermeldungen sind verständlich, sachlich und konstruktiv zu formulieren und sind einheitlich zu strukturieren (z.B. Fehlerart, Fehlerursachen, Fehlerbehebung). • Wird ein Vorgang vom Benutzer versehentlich abgebrochen, so kann er später an der aktuellen Stelle fortgesetzt werden, so dass der Benutzer nicht zum Anfang zurückkehren muss. 7.2.6 Individualisierbarkeit Ein Dialog ist individualisierbar, wenn das Dialogsystem Anpassungen an die Erfordernisse der Arbeitsaufgabe sowie an die individuellen Fähigkeiten und Vorlieben des Benutzers zulässt. • Anpassbarkeit an Sprache und kulturelle Eigenheiten des Benutzers, z.B. durch unterschiedliche Tastenbelegungen. • Anpassbarkeit an das Wahrnehmungsvermögen und die sensomotorischen Fähigkeiten, z.B. durch Wahl der Schriftgröße, Wahl der Farben für farbenfehlsichtige Benutzer, Zuordnung der linken und rechten Maustaste. • Wahl unterschiedlicher Informations-Darstellungsformen. • Möglichkeit, eigenes Vokabular zu benutzen, um eigene Bezeichnungen für Objekte und Arbeitsabläufe festzulegen. • Möglichkeiten, eigene Kommandos zu ergänzen, z.B. durch programmierbare Funktionstasten und Aufzeichnung von Kommandofolgen. • Die Benutzer können Präferenzen setzen oder Lesezeichen und Anmerkungen verwenden. 7.2.7 Lernförderlichkeit Ein Dialog ist lernförderlich, wenn er den Benutzer beim Erlernen des Dialogsystems unterstützt und anleitet. • Darstellung der zugrunde liegenden Regeln und Konzepte, die für das Erlernen nützlich sind. • Unterstützung relevanter Lernstrategien, z.B. learning by doing. • Wiederauffrischen von Gelerntem unterstützen, z.B. selbsterklärende Gestaltung selten benutzter Kommandos. • Regelhaft und einheitliche Benutzungsoberfläche, z.B. gleichartige Hinweismeldungen erscheinen immer am gleichen Ort. • Dem Benutzer stehen verschiedene Navigationsmöglichkeiten zur Verfügung, die er singulär oder kombiniert verwenden kann. 7.3 Acht goldene Regeln für die Dialoggestaltung Die folgenden acht goldenen Regeln fassen die wichtigsten Eigenschaften guter Dialogsysteme noch mal zusammen [23]: 1. Versuche Konsistenz zu erreichen. Aus ähnlichen Situationen sollten ähnliche Aktionsfolgen resultieren. In Prompts, Menüs und Hilfeinformationen sollten identische Begriffe verwendet werden. 7.3. ACHT GOLDENE REGELN FÜR DIE DIALOGGESTALTUNG 95 2. Biete erfahrenen Benutzern Abkürzungen an. Je häufiger ein System benutzt wird, desto größer ist der Wunsch, nach weniger Interaktionen, um schneller vorwärts zu kommen. Abkürzungen, Funktionstasten, versteckte Kommandos und Makros sind nützliche Hilfsmittel, dies zu erreichen. 3. Biete informatives Feedback. Jede Aktion sollte eine sichtbare Systemreaktion bewirken, wobei sich der Umfang des Feedbacks an der Tragweite der Aktion orientieren sollte. Die Visualisierung der Arbeitsobjekte ist eine gute Möglichkeit, Änderungen zu verdeutlichen. 4. Dialoge sollten abgeschlossen sein. Aktionsfolgen sollten einen Beginn, eine Mitte und ein Ende besitzen. Benutzer erkennen dadurch einzelne Abfolgen und können sich nach dem Ende einer solchen Abfolge ganz auf die nächste Abfolge konzentrieren. 5. Biete einfache Fehlerbehandlung. Es sollte grundsätzlich nicht möglich sein, schwerwiegende Fehler zu begehen. Falls ein Fehler passiert ist, sollte das System diesen erkennen und eine einfache Fehlerbehebung anbieten. 6. Biete einfache Rücksetzmöglichkeiten. Aktionen sollte zurücknehmbar sein. Die nimmt dem Benutzer die Angst bei der Arbeit, da jederzeit die Sicherheit besteht, zum Zustand vor einer Aktion zurückkehren zu können. 7. Unterstütze benutzergesteuerten Dialog. Erfahrene Benutzer wollen das Gefühl haben, den Dialog im Griff zu haben. Unerwartete Systemreaktionen, lange Dateneingabesequenzen, Schwierigkeiten, benötigte Informationen abzurufen oder gewünschte Aktionen zu initiieren, führen zu Angst und Unzufriedenheit. 8. Reduziere die Belastung des Kurzzeitgedächtnisses. Die Beschränkung des menschlichen Kurzzeitgedächtnisses erfordert relativ einfache Bildschirminhalte sowie Hilfemöglichkeiten für Syntaxanforderungen, Abkürzungen und Codes. 96 KAPITEL 7. ERGONOMISCHE ASPEKTE Kapitel 8 Java Abstract Windowing Toolkit (AWT) Das Abstract Windowing Toolkit (AWT) ist eine Sammlung von Java-Klassen zur Konstruktion graphischer Benutzungsoberflächen, das in frühen Java-Versionen benutzt wurde. Die aktuellen Versionen benutzen eine Weiterentwicklung mit dem Namen Swing, die im nächsten Kapitel vorgestellt wird. Da AWT nur noch von älteren Java-Programmen bzw. in Umgebungen verwendet wird, in denen nur eine alte Java-Version zur Verfügung steht, beschränkt sich dieses Kapitel lediglich auf einige Grundlagen. 8.1 AWT Komponenten Abbildung 8.1 zeigt die einzelnen Komponenten des AWT, aus denen graphische Benutzungsoberflächen zusammengesetzt werden. Abbildung 8.1: AWT-Komponenten für graphische Benutzungsoberflächen Die entsprechenden Klassen für die Komponenten sind in Abbildung 8.2 dargestellt: 97 98 KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT) Button Canvas Checkbox Dialog Choice FileDialog Window Frame Container Panel Label ScrollPane Component List Scrollbar Object TextArea TextComponent TextField CheckboxMenuItem MenuItem MenuComponent Menu PopupMenu Menubar Abbildung 8.2: AWT-Klassen zur Konstruktion graphischer Benutzungsoberflächen Die Implementierung von AWT setzt auf die APIs der verschiedenen Plattformen auf. Dadurch bekommt man das auf der Plattform typische Look and Feel“. Allerdings ist die Abbildung von AWT auf die zugrunde ” liegende API nicht immer einfach und unproblematisch. 8.2 Ereignisorientiere Programmierung • Ein klassisches“ Programm liest Eingaben, die es durch eine sequentielle (nicht-nebenläufige) Abar” beitung von Anweisungen verarbeitet und dabei Ausgaben erzeugt (z.B. Sortieren einer Menge von Datensätzen, Bearbeitung einer Datenbankanfrage). • Ein ereignisorientiertes Programm wartet in einer Hauptschleife auf Ereignisse (Taste gedrückt, Maus bewegt, Daten empfangen) und führt, abhängig vom eingetretenen Ereignis und dem aktuellen Zustand des Programms, eine Sequenz von Anweisungen aus und kehrt danach wieder in die Hauptschleife zurück (z.B. Graphische Oberflächen, Simulationsprogramme). • Die Hauptschleife, die man meist nicht selbst programmieren muss, hat im Wesentlichen folgenden logischen Aufbau: while (true) { 99 8.2. EREIGNISORIENTIERE PROGRAMMIERUNG Object EventObject AWTEvent ActionEvent ContainerEvent AdjustmentEvent ComponentEvent FocusEvent KeyEvent InputEvent ItemEvent PaintEvent TextEvent WindowEvent MouseEvent Abbildung 8.3: AWT Ereignisse EventObject e = waitForNextEvent(); processEvent(e); } • Das Programm sollte in kleine“ Funktionen zerlegt werden, die beim Eintreffen von Ereignissen ” ausgeführt werden und meist nur relativ einfache Aktionen bewirken. • Beim Verarbeiten von Ereignissen ist zu bestimmen, welche der implementierten kleinen“ Funktio” nen für das Ereignis zuständig ist. • Komplexere Aktionen oder Aktionen, die potentiell blockieren können, sind mit besonderer Sorgfalt zu behandeln, da ansonsten die Hauptschleife zu lange unterbrochen wird und das Programm ein” friert“. Ereignisse werden in Java durch spezielle Klassen dargestellt (Abbildung 8.3). Ein kleines Beispiel-Programm... // events1/ActionExample.java import java.awt.Frame; import java.awt.Button; public class ActionExample extends Frame { public ActionExample() { Button button = new Button("press me"); add(button); } public static void main(String args[]) { 100 KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT) Frame f = new ActionExample(); f.pack(); f.show(); } } Bemerkungen: • Das Programm implementiert die Klasse ActionExample als eine Erweiterung der Klasse Frame (ein einfacher Rahmen). • Im Konstruktor wird ein Button erzeugt und in den Rahmen eingefügt. • Frage: Was passiert wenn der Button gedrückt wird? Klassen, die so genannte EventListener Interfaces (Abbildung 8.4) implementieren, können auf passende Ereignisse reagieren. «interface» ActionListener «interface» AdjustmentListener «interface» ComponentListener «interface» FocusListener «interface» ContainerListener «interface» EventListener «interface» ItemListener «interface» KeyListener «interface» TextListener «interface» MouseMotionListener «interface» MouseListener «interface» WindowListener Abbildung 8.4: AWT Listener Interfaces Damit lässt sich das Beispielprogramm vervollständigen: // events2/ActionExample.java import java.awt.Frame; 8.2. EREIGNISORIENTIERE PROGRAMMIERUNG 101 import java.awt.Button; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class ActionExample extends Frame { public ActionExample() { Button button = new Button("press me"); add(button); button.addActionListener(new ButtonActionListener()); } class ButtonActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("button pressed"); } } public static void main(String args[]) { Frame f = new ActionExample(); f.pack(); f.show(); } } Bemerkungen: • Die Klasse ButtonActionListener implementiert das ActionListener Interface. • Das ActionListener Interface erfordert von der Klasse ActionExample die Implementierung der Methode actionPerformed(ActionEvent e). • Der Konstruktor ActionExample erzeugt eine Instanz der ButtonActionListener Klasse zum Verarbeiten von ActionEvents. • Die ButtonActionListener Instanz wird beim button eingetragen, damit später die entsprechenden ActionEvents des button bearbeitet werden können. Kann ein Button auch andere Events generieren? // events3/ActionExample.java import import import import import import java.awt.Frame; java.awt.Button; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.event.MouseEvent; java.awt.event.MouseListener; public class ActionExample extends Frame { 102 KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT) public ActionExample() { Button button = new Button("press me"); add(button); button.addActionListener(new ButtonListener()); button.addMouseListener(new ButtonMouseListener()); } class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("button pressed"); } } class ButtonMouseListener implements MouseListener { public void mouseEntered(MouseEvent e) { System.out.println("mouse entered button"); } public void mouseExited(MouseEvent e) { System.out.println("mouse exited button"); } public void mousePressed(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} } public static void main(String args[]) { Frame f = new ActionExample(); f.pack(); f.show(); } } Bemerkungen: • Das MouseListener Interface verlangt die Implementation von fünf Methoden: – Eintritt des Mauszeigers: mouseEntered(MouseEvent e) – Austritt des Mauszeigers: mouseExited(MouseEvent e) – Drücken einer Maustaste: mousePressed(MouseEvent e) – Klicken einer Maustaste: mouseClicked(MouseEvent e) – Loslassen einer Maustaste: mouseReleased(MouseEvent e) • Die meisten der MouseListener Methoden sind hier offenbar leer“. ” • Frage: Kann man sich die Implementation leerer“ Methoden nicht irgendwie sparen? ” Adapter-Klassen sind leere“ EventListener. Mit Hilfe von Adapter-Klassen kann die Implementierung ” verkürzt werden, da man keine leeren“ Methoden realisieren muss: ” 8.2. EREIGNISORIENTIERE PROGRAMMIERUNG // events4/ActionExample.java import import import import import import java.awt.Frame; java.awt.Button; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.event.MouseEvent; java.awt.event.MouseAdapter; public class ActionExample extends Frame { public ActionExample() { Button button = new Button("press me"); add(button); button.addActionListener(new ButtonListener()); button.addMouseListener(new ButtonMouseAdapter()); } class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("button pressed"); } } class ButtonMouseAdapter extends MouseAdapter { public void mouseEntered(MouseEvent e) { System.out.println("mouse entered button"); } public void mouseExited(MouseEvent e) { System.out.println("mouse exited button"); } } public static void main(String args[]) { Frame f = new ActionExample(); f.pack(); f.show(); } } Mit Hilfe von anonymen Klassen lässt sich das Programm noch etwas kompakter schreiben: // events5/ActionExample.java import import import import java.awt.Frame; java.awt.Button; java.awt.event.ActionEvent; java.awt.event.ActionListener; 103 104 KAPITEL 8. JAVA ABSTRACT WINDOWING TOOLKIT (AWT) import java.awt.event.MouseEvent; import java.awt.event.MouseAdapter; public class ActionExample extends Frame { public ActionExample() { Button button = new Button("press me"); add(button); button.addActionListener(new ButtonListener()); button.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { System.out.println("mouse entered button"); } public void mouseExited(MouseEvent e) { System.out.println("mouse exited button"); } }); } class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("button pressed"); } } public static void main(String args[]) { Frame f = new ActionExample(); f.pack(); f.show(); } } Ob sich durch diese kompaktere Darstellung die Lesbarkeit der Quelltextes verbessert hat, kann allerdings diskutiert werden. Kapitel 9 Java Foundation Classes (Swing) Hinter dem Namen Swing [28] verbirgt sich eine Sammlung von Java-Klassen zur Implementation von graphischen Benutzungsoberflächen, die Teil der Java Foundation Classes (JFC) ist. Die Swing-Klassen, deren Namen (fast) alle mit einem großen J beginnen, ersetzen die vorher benutzten AWT-Komponenten. Die Swing-Klassen selbst basieren jedoch auf Teilen der grundlegenden AWT-Infrastruktur, weshalb auch in Zukunft AWT-Komponenten vorhanden sein werden. Eine Übersicht über die Klassen gibt die Abbildung 9.1. Button Canvas JDialog Dialog Checkbox FileDialog Window Choice Frame JFrame Applet JApplet JMenu Container Panel JMenuItem JCheckBoxMenuItem Component Label ScrollPane JMenuBar AbstractButton List JComponent JRadioButtonMenuItem JButton JComboBox JLabel Scrollbar JCheckButton JToggleButton JList Object TextArea JOptionPane TextField JPanel TextComponent JRadioButton JScrollPane JSplitPane CheckboxMenuItem JTabbedPane JFormattedTextField JTextField JTextComponent JPasswordField MenuItem JToolBar MenuComponent Menu JTextArea PopupMenu JEditorPane JTextPane Menubar Abbildung 9.1: Swing-Klassen zur Konstruktion graphischer Benutzungsoberflächen In den folgenden Abschnitten werden anhand von Beispielen Swing Konzepte und Klassen vorgestellt. Die Beispiele wurden alle mit Java Version 1.3 getestet. Einige Beispiele laufen nur mit Änderungen auf älteren Java Versionen. 105 106 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) 9.1 Einführende Beispiele Zunächst ein paar kleine einführende Beispiele, die ein grundlegendes Verständnis für die Programmierung mit Java Swing wecken sollen. 9.1.1 Hello World Mit einem Hello World“-Programm fängt immer alles an... ” Abbildung 9.2: HelloWorldSwing // hello1/HelloWorldSwing.java -import javax.swing.JFrame; import javax.swing.JLabel; public class HelloWorldSwing { public static void main(String[] args) { final JFrame frame = new JFrame("HelloWorldSwing"); final JLabel label = new JLabel("Hello World"); frame.getContentPane().add(label); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } } Bemerkungen: • Erzeugt ein top-level Fenster (JFrame) mit dem Titel HelloWorldSwing“. ” • Erzeugt ein JLabel-Objekt mit dem Inhalt Hello World“ und fügt dieses Objekt in die ContentPane ” des JFrame-Objekts ein. • Beim Löschen des Fensters soll das Programm beendet werden. • Zum Schluss werden die Elemente im Frame angeordnet und sichtbar gemacht. Eine abgewandelte Version des Hello World“-Programms mit expliziter Ereignisverarbeitung zur Behand” lung von WindowEvents. // hello2/HelloWorldSwing.java -import import import import java.awt.event.WindowEvent; java.awt.event.WindowAdapter; javax.swing.JFrame; javax.swing.JLabel; 9.1. EINFÜHRENDE BEISPIELE 107 public class HelloWorldSwing { public static void main(String[] args) { final JFrame frame = new JFrame("HelloWorldSwing"); final JLabel label = new JLabel("<html><i>Hello</i> " + "<b>World</b>!</html>"); frame.getContentPane().add(label); frame.pack(); frame.setVisible(true); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } Bemerkungen: • Explizite programmierte Reaktion auf WindowEvents durch eine anonyme innere Klasse, die die Methode windowClosing() der Adapter-Klasse WindowAdapter überschreibt. • Ereignisorientierte Programmierung basiert auf AWT, was sich auch in den import-Zeilen ausdrückt. • Achtung: Labels können auch HTML enthalten!?! 9.1.2 Celsius Converter Der CelsiusConverter ist ein einfaches Programm, das Werte in Grad Celsius entgegen nimmt und den entsprechenden Wert in Grad Fahrenheit darstellt. Abbildung 9.3: CelsiusConverter // celsius1/CelsiusConverter.java -import import import import import import import import import import java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.BorderLayout; java.awt.GridLayout; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JLabel; javax.swing.JButton; javax.swing.JTextField; javax.swing.SwingConstants; 108 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) public class CelsiusConverter { JFrame frame; JPanel panel; JLabel celLabel, fahLabel, resLabel; JButton convButton, exitButton; JTextField inputTextField; public CelsiusConverter() { frame = new JFrame("Convert Celsius to Fahrenheit"); panel = new JPanel(); panel.setLayout(new GridLayout(3, 2)); addWidgets(); frame.getContentPane().add(panel, BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } private void addWidgets() { celLabel = new JLabel("Celsius:", SwingConstants.LEFT); fahLabel = new JLabel("Fahrenheit:", SwingConstants.LEFT); resLabel = new JLabel("", SwingConstants.CENTER); convButton = new JButton("Convert"); exitButton = new JButton("Exit"); inputTextField = new JTextField(); convButton.addActionListener(new ConvertListener()); exitButton.addActionListener(new ExitListener()); panel.add(celLabel); panel.add(inputTextField); panel.add(fahLabel); panel.add(resLabel); panel.add(convButton); panel.add(exitButton); } class ConvertListener implements ActionListener { public void actionPerformed(ActionEvent event) { double f; try { f = Double.parseDouble(inputTextField.getText()); f = f * 1.8 + 32; } catch (NumberFormatException e) { resLabel.setText("illegal input"); return; } resLabel.setText((int) f + " F"); } } 9.1. EINFÜHRENDE BEISPIELE 109 class ExitListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.exit(0); } } public static void main(String[] args) { CelsiusConverter converter = new CelsiusConverter(); } } Bemerkungen: • Ein JPanel kann andere Komponenten aufnehmen. • Die Anordnung der Komponenten im JPanel wird durch einen sogenannten Layout Manager bestimmt (GridLayout in diesem Beispiel). • Ein JButton stellt eine einfache Schaltfläche zur Verfügung. • Ein JTextField nimmt textuelle Eingaben von Benutzer entgegen. • Für die beiden JButton wird jeweils ein ActionListener definiert. • Man beachte die Fehlerbehandlung bei ungültigen Eingaben. Tauscht man im CelsiusConverter die Klasse ConvertListener durch folgende Implementation aus, so wird im Fehlerfall ein modaler Dialog angezeigt. // celsius2/CelsiusConverter.java -import import import import import import import import import import import java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.BorderLayout; java.awt.GridLayout; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JLabel; javax.swing.JButton; javax.swing.JTextField; javax.swing.JOptionPane; javax.swing.SwingConstants; public class CelsiusConverter { JFrame frame; JPanel panel; JLabel celLabel, fahLabel, resLabel; JButton convButton, exitButton; JTextField inputTextField; public CelsiusConverter() { 110 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) frame = new JFrame("Convert Celsius to Fahrenheit"); panel = new JPanel(); panel.setLayout(new GridLayout(3, 2)); addWidgets(); frame.getContentPane().add(panel, BorderLayout.CENTER); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } private void addWidgets() { celLabel = new JLabel("Celsius:", SwingConstants.LEFT); fahLabel = new JLabel("Fahrenheit:", SwingConstants.LEFT); resLabel = new JLabel("", SwingConstants.CENTER); convButton = new JButton("Convert"); exitButton = new JButton("Exit"); inputTextField = new JTextField(); convButton.addActionListener(new ConvertListener()); exitButton.addActionListener(new ExitListener()); panel.add(celLabel); panel.add(inputTextField); panel.add(fahLabel); panel.add(resLabel); panel.add(convButton); panel.add(exitButton); } class ConvertListener implements ActionListener { public void actionPerformed(ActionEvent event) { double f; try { f = Double.parseDouble(inputTextField.getText()); f = f * 1.8 + 32; } catch (NumberFormatException e) { JOptionPane.showMessageDialog(frame, "illegal input \"" + inputTextField.getText() +"\"", "Illegal Input", JOptionPane.ERROR_MESSAGE); resLabel.setText(""); return; } resLabel.setText((int) f + " F"); } } class ExitListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.exit(0); } } 9.1. EINFÜHRENDE BEISPIELE 111 public static void main(String[] args) { CelsiusConverter converter = new CelsiusConverter(); } } Bemerkungen: • Mit Hilfe der Klasse JOptionPane können Standarddialoge erzeugt werden. • Die Methode showMessageDialog() ist eine Klassenmethode. • Ein Dialog wird an einem top-level Object (hier ein JFrame) verankert. 9.1.3 Look and Feel Swing Programme können ein unterschiedliches Erscheinungsbild haben (Look and Feel). Das folgende Programm demonstriert diese Fähigkeit. Abbildung 9.4: LookAndFeelJava Abbildung 9.5: LookAndFeelMotif // looknfeel/LookAndFeel.java import import import import import import import import import import import java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.event.WindowEvent; java.awt.event.WindowAdapter; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JButton; javax.swing.JRadioButton; javax.swing.ButtonGroup; javax.swing.SwingUtilities; javax.swing.UIManager; public class LookAndFeel extends JPanel { static JFrame frame; static final String metal= "Metal"; static final String metalClassName = 112 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) "javax.swing.plaf.metal.MetalLookAndFeel"; static String motif = "Motif"; static String motifClassName = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; static String windows = "Windows"; static String windowsClassName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; JRadioButton metalButton, motifButton, windowsButton; public LookAndFeel() { JButton button = new JButton("Hello, world"); button.setMnemonic(’h’); metalButton = new JRadioButton(metal); metalButton.setMnemonic(’t’); metalButton.setActionCommand(metalClassName); motifButton = new JRadioButton(motif); motifButton.setMnemonic(’m’); motifButton.setActionCommand(motifClassName); windowsButton = new JRadioButton(windows); windowsButton.setMnemonic(’w’); windowsButton.setActionCommand(windowsClassName); ButtonGroup group = new ButtonGroup(); group.add(metalButton); group.add(motifButton); group.add(windowsButton); RadioListener myListener = new RadioListener(); metalButton.addActionListener(myListener); motifButton.addActionListener(myListener); windowsButton.addActionListener(myListener); add(button); add(metalButton); add(motifButton); add(windowsButton); } class RadioListener implements ActionListener { public void actionPerformed(ActionEvent e) { String lnfName = e.getActionCommand(); try { UIManager.setLookAndFeel(lnfName); SwingUtilities.updateComponentTreeUI(frame); frame.pack(); 9.1. EINFÜHRENDE BEISPIELE 113 } catch (Exception exc) { JRadioButton button = (JRadioButton) e.getSource(); button.setEnabled(false); updateState(); System.err.println("LookAndFeel: " + exc.getMessage()); } } } public void updateState() { String lnfName = UIManager.getLookAndFeel().getClass().getName(); if (lnfName.indexOf(metal) >= 0) { metalButton.setSelected(true); } else if (lnfName.indexOf(windows) >= 0) { windowsButton.setSelected(true); } else if (lnfName.indexOf(motif) >= 0) { motifButton.setSelected(true); } else { System.err.println("LookAndFeel: Unknown LookAndFeel " + lnfName); } } public static void main(String s[]) { LookAndFeel panel = new LookAndFeel(); frame = new JFrame("LookAndFeel"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); frame.getContentPane().add("Center", panel); frame.pack(); frame.setVisible(true); panel.updateState(); } } Bemerkungen: • Die Klasse LookAndFeel ist eine Spezialisierung des JPanel. • Neben einem JButton werden drei JRadioButton erzeugt, die in einer ButtonGroup zusammengefasst werden. • Jeder JRadioButton bekommt einen ActionListener installiert. • Der RadioListener versucht das verwendete Look and Feel zu setzen. Im Fehlerfall wird der entsprechende Knopf ausgeschaltet. • Die Methode updateState() selektiert den JRadioButton, der dem aktuellen Look and Feel entspricht. 114 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) • Die Methode main() erzeugt einen JFrame und eine LookAndFeel Instanz, die in die ContentPane des JFrame eingetragen wird. 9.1.4 Root Panes Abbildung 9.6: Swing Root Panes Bemerkungen: • Jede top-level Komponente stellt ein sogenanntes RootPane zur Verfügung, das selbst aus mehreren Panes besteht. • Das GlassPane ist normalerweise vollkommen transparent. Es dient lediglich dazu, Ereignisse für das gesamte RootPane abzufangen. • In dem ContentPane werden die eigentlichen Komponenten der Applikation untergebracht. • Die Menüs einer Applikation werden im optionalen JMenuBar unterbracht und nicht etwa in der ContentPane. • Das LayeredPane wird benutzt, um die ContentPane und das optionale Menü zu positionieren. Man kann es auch benutzen, um andere Komponenten (z.B. Popup-Menüs) über anderen Komponenten zu platzieren. 9.1.5 Threads Obwohl Java mehrere nebenläufige Threads unterstützt, gibt es in Swing deutliche Einschränkungen, da die Swing-Klassen nicht selbst die erforderliche Synchronisation realisieren: • Es gibt einen speziellen event dispatching thread“, der für die Abarbeitung von Swing-Ereignissen ” zuständig ist. • Die Methoden, die bei der Bearbeitung von Ereignissen aufgerufen werden, sollten keine längeren Berechnungen anstellen, da sonst die Oberfläche bis zum Ende der Berechnung einfriert“. ” • Nachdem eine Swing-Komponente realisiert wurde (d.h., sie wurde auf dem Ausgabegerät dargestellt), darf generell nur der event dispatching thread“ die Komponente aktualisieren. Mit anderen Worten, ” Swing ist bis auf wenige Methoden nicht thread-safe. • Andere Threads können mit den Methoden invokeLater() und invokeAndWait() der Klasse SwingUtilities den event dispatching thread“ zur Ausführung von Methoden veranlassen, ” sobald alle übrigen Ereignisse abgearbeitet worden sind. 115 9.2. LAYOUT MANAGER 9.2 Layout Manager Ein Layout Manager arrangiert Komponenten in einem Container. Die Schnittstelle eines Layout Managers ist durch ein Interface festgelegt. FlowLayout «interface» LayoutManager +addLayoutComponent(name:String,comp:Component): void +layoutContainer(parent:Container): void +minimumLayoutSize(parent:Container): Dimension +preferredLayoutSize(parent:Container): Dimension +removeLayoutComponent(comp:Component): void GridLayout BorderLayout Object BoxLayout «interface» LayoutManager2 GridBagLayout +addLayoutComponent(comp:Component,constraints:Object): void +invalidateLayout(target:Container): void +maximumLayoutSize(target:Container): Dimension +getLayoutAlignmentX(target:Container): float +addLayoutAlignmentY(target:Container): float CardLayout Abbildung 9.7: Swing Layout Manager Klassen und Interfaces Bemerkungen: • Manche Layout-Algorithmen laufen in zwei getrennten Phasen ab. • Die geforderten Methoden minimumLayoutSize(), preferredLayoutSize() und maximumLayoutSize() liefern die minimale, normale oder maximale Größe für einen Container. • Die Layout-Algorithmen können diese Werte benutzen, um für ineinander geschachtelte Container ein Layout zu ermitteln, wobei jeder Container einen anderen Layout-Algorithmus benutzen kann. • Die Methode layoutContainer bewirkt schließlich die Berechnung eines Layouts. • Es gibt eine Vielzahl von Layout-Manager Klassen, die verschiedene Algorithmen realisieren. 9.2.1 Border Layout Die Klasse BorderLayout realisiert einen sehr einfachen Algorithmus, bei dem bis zu fünf Komponenten in den Positionen north, south, west, east und center platziert werden können. Abbildung 9.8: BorderLayoutDemo // borderlayout/BorderLayoutDemo.java import java.awt.BorderLayout; 116 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) import java.awt.Container; import javax.swing.JFrame; import javax.swing.JButton; public class BorderLayoutDemo extends JFrame { public BorderLayoutDemo() { Container contentPane = getContentPane(); // install a new BorderLayout with hgap = 0 and vgap = 10 contentPane.setLayout(new BorderLayout(0, 10)); contentPane.add(new JButton("Button 1 (NORTH)"), BorderLayout.NORTH); contentPane.add(new JButton("2 (CENTER)"), BorderLayout.CENTER); contentPane.add(new JButton("Button 3 (WEST)"), BorderLayout.WEST); contentPane.add(new JButton("Long-Named Button 4 (SOUTH)"), BorderLayout.SOUTH); contentPane.add(new JButton("Button 5 (EAST)"), BorderLayout.EAST); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[]) { BorderLayoutDemo window = new BorderLayoutDemo(); window.setTitle("BorderLayoutDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Beim verändern der Größe wird übrig bleibender Platz der Mitte zugeordnet, d.h. die größte Fläche entsteht in der Mitte. • Die optionalen Argumente definieren einen horizontalen bzw. einen vertikalen Abstand zwischen den Komponenten. 9.2.2 Box Layout Die Klasse BoxLayout realisiert einen Algorithmus, bei dem Komponenten entweder übereinander (von oben nach unten) oder nebeneinander (von links nach rechts) angeordnet werden, wobei die Ausrichtung der Komponenten und deren minimale, bevorzugte und maximale Größe berücksichtigt wird. 117 9.2. LAYOUT MANAGER Abbildung 9.9: BoxLayoutDemo // boxlayout1/BoxLayoutDemo.java import import import import import import import java.awt.Container; java.awt.Component; java.awt.Dimension; javax.swing.Box; javax.swing.BoxLayout; javax.swing.JFrame; javax.swing.JButton; public class BoxLayoutDemo extends JFrame { public BoxLayoutDemo() { Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); addAButton("Button 1"); addAButton("2"); addAButton("Button 3"); addAButton("Long-Named Button 4"); addAButton("Button 5"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } private void addAButton(String text) { JButton button = new JButton(text); button.setAlignmentX(Component.CENTER_ALIGNMENT); getContentPane().add(button); } public static void main(String args[]) { BoxLayoutDemo window = new BoxLayoutDemo(); window.setTitle("BoxLayoutDemo"); window.pack(); window.setVisible(true); } } 118 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) Bemerkungen: • Verfügbarer Platz, der nicht von den Komponenten benötigt wird, wird am Ende (in Layout-Richtung gesehen) untergebracht. • Mit Hilfe der Methoden setAlignmentX() und setAlignmentY() können Komponenten bestimmen, ob sie linksbündig, zentriert oder rechtsbündig dargestellt werden. Die definierten Konstanten LEFT ALIGNMENT, CENTER ALIGNMENT und RIGHT ALIGNMENT entsprechen den Werten 0.0, 0.5 und 1.0. • Zusätzliche unsichtbare Komponenten (sogenannte Boxen) können benutzt werden, um Abstände zwischen benachbarten Komponenten festzulegen. • Boxen mit einer festen Größe (rigid area) erzeugen einen festen Abstand zwischen Komponenten. • Boxen mit variabler horizontaler oder vertikaler Größe (glue area) nehmen übrigen Platz auf. • Füllboxen (filler) sind selbstdefinierte Boxen, die eine minimale, bevorzugte und maximale Größe festlegen. Eine Erweiterung des Beispiels mit Boxen: // boxlayout2/BoxLayoutDemo.java import import import import import import import java.awt.Container; java.awt.Component; java.awt.Dimension; javax.swing.Box; javax.swing.BoxLayout; javax.swing.JFrame; javax.swing.JButton; public class BoxLayoutDemo extends JFrame { public BoxLayoutDemo() { Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); addAButton("Button 1"); // rigid area contentPane.add(Box.createRigidArea(new Dimension(0, 10))); addAButton("2"); // custom filler Dimension minSize = new Dimension(222, 5); Dimension prefSize = new Dimension(222, 10); Dimension maxSize = new Dimension(222, 20); contentPane.add(new Box.Filler(minSize, prefSize, maxSize)); addAButton("Button 3"); // glue contentPane.add(Box.createVerticalGlue()); addAButton("Long-Named Button 4"); // glue contentPane.add(Box.createVerticalGlue()); addAButton("Button 5"); 9.2. LAYOUT MANAGER 119 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } private void addAButton(String text) { JButton button = new JButton(text); button.setAlignmentX(Component.CENTER_ALIGNMENT); getContentPane().add(button); } public static void main(String args[]) { BoxLayoutDemo window = new BoxLayoutDemo(); window.setTitle("BoxLayoutDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Die selbstdefinierte Füllbox erzwingt eine Breite von mindestens 222 Punkten. Die Höhe bewegt sich im Bereich 5-20 Punkte mit einem bevorzugten Wert von 10 Punkten. • Sind mehrere variable Boxen vorhanden, dann wird der übrige Platz zwischen den Boxen aufgeteilt. 9.2.3 Card Layout Die Klasse CardLayout realisiert einen Algorithmus, bei dem mehrere Komponenten quasi auf einem Stapel übereinander angeordnet werden, wobei immer nur die oberste Komponente sichtbar ist. Abbildung 9.10: CardLayoutDemo (Buttons) Abbildung 9.11: CardLayoutDemo (Textfield) // cardlayout/CardLayoutDemo.java import java.awt.CardLayout; import java.awt.BorderLayout; 120 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) import import import import import import import import java.awt.Container; java.awt.event.ItemEvent; java.awt.event.ItemListener; javax.swing.JFrame; javax.swing.JButton; javax.swing.JPanel; javax.swing.JTextField; javax.swing.JComboBox; public class CardLayoutDemo extends JFrame implements ItemListener { JPanel cards; final static String BUTTONPANEL = "JPanel with JButtons"; final static String TEXTPANEL = "JPanel with JTextField"; public CardLayoutDemo() { Container contentPane = getContentPane(); String comboBoxItems[] = { BUTTONPANEL, TEXTPANEL }; JComboBox c = new JComboBox(comboBoxItems); c.setEditable(false); c.addItemListener(this); JPanel cbp = new JPanel(); cbp.add(c); contentPane.add(cbp, BorderLayout.NORTH); cards = new JPanel(); cards.setLayout(new CardLayout()); JPanel p1 = new JPanel(); p1.add(new JButton("Button 1")); p1.add(new JButton("Button 2")); p1.add(new JButton("Button 3")); JPanel p2 = new JPanel(); p2.add(new JTextField("TextField", 20)); cards.add(p1, BUTTONPANEL); cards.add(p2, TEXTPANEL); contentPane.add(cards, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void itemStateChanged(ItemEvent e) { CardLayout cl = (CardLayout) (cards.getLayout()); cl.show(cards, (String) e.getItem()); } public static void main(String args[]) { CardLayoutDemo window = new CardLayoutDemo(); 121 9.2. LAYOUT MANAGER window.setTitle("CardLayoutDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Die einzelnen Komponenten auf dem Stapel werden über einen Namen identifiziert. • Komponenten können über den Namen direkt oben auf den Stapel gelegt werden. • Alternativ kann durch den Stapel vorwärts und rückwärts hindurch gelaufen werden. • Wird kaum noch benutzt, da ein JTabbedPane in den meisten Fällen einfacher zu benutzen ist. Abbildung 9.12: TabbedPaneDemo // tabbedpane/TabbedPaneDemo.java import import import import import import import import import java.awt.Container; java.awt.BorderLayout; java.awt.Dimension; java.awt.Insets; javax.swing.JFrame; javax.swing.JButton; javax.swing.JPanel; javax.swing.JTextField; javax.swing.JTabbedPane; public class TabbedPaneDemo extends JFrame { final static String BUTTONPANEL = "JPanel with JButtons"; final static String TEXTPANEL = "JPanel with JTextField"; public TabbedPaneDemo() { Container contentPane = getContentPane(); JTabbedPane tabbedPane = new JTabbedPane(); JPanel p1 = new JPanel();; p1.add(new JButton("Button 1")); p1.add(new JButton("Button 2")); p1.add(new JButton("Button 3")); tabbedPane.addTab(BUTTONPANEL, p1); JPanel p2 = new JPanel(); 122 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) p2.add(new JTextField("TextField", 20)); tabbedPane.addTab(TEXTPANEL, p2); contentPane.add(tabbedPane, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[]) { TabbedPaneDemo window = new TabbedPaneDemo(); window.setTitle("TabbedPaneDemo"); window.pack(); window.setSize(400, 120); window.setVisible(true); } } 9.2.4 Flow Layout Die Klasse FlowLayout realisiert einen einfachen Algorithmus, bei dem die Komponenten in ihrer bevorzugten Größe in einer Zeile von links nach rechts angeordnet werden. Abbildung 9.13: FlowLayoutDemo // flowlayout/FlowLayoutDemo.java import import import import java.awt.FlowLayout; java.awt.Container; javax.swing.JFrame; javax.swing.JButton; public class FlowLayoutDemo extends JFrame { public FlowLayoutDemo() { Container contentPane = getContentPane(); contentPane.setLayout(new FlowLayout(FlowLayout.LEFT)); contentPane.add(new contentPane.add(new contentPane.add(new contentPane.add(new contentPane.add(new JButton("Button 1")); JButton("2")); JButton("Button 3")); JButton("Long-Named Button 4")); JButton("Button 5")); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[]) 123 9.2. LAYOUT MANAGER { FlowLayoutDemo window = new FlowLayoutDemo(); window.setTitle("FlowLayoutDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Wenn nicht alle Komponenten in einer Zeile untergebracht werden können, dann werden zusätzliche Zeilen angelegt. • Der optionale Parameter im Konstruktor FlowLayout bestimmt, wie die Komponenten in den Zeilen zueinander angeordnet werden. 9.2.5 Grid Layout Die Klasse GridLayout implementiert einen Algorithmus, der Komponenten tabellenförmig darstellt. Die einzelnen Zellen bekommen alle dieselbe Größe zugeordnet. Abbildung 9.14: GridLayoutDemo // gridlayout/GridLayoutDemo.java import import import import java.awt.Container; java.awt.GridLayout; javax.swing.JFrame; javax.swing.JButton; public class GridLayoutDemo extends JFrame { public GridLayoutDemo() { Container contentPane = getContentPane(); contentPane.setLayout(new GridLayout(0, 2, 10, 10)); contentPane.add(new contentPane.add(new contentPane.add(new contentPane.add(new contentPane.add(new JButton("Button 1")); JButton("2")); JButton("Button 3")); JButton("Long-Named Button 4")); JButton("Button 5")); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } 124 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) public static void main(String args[]) { GridLayoutDemo window = new GridLayoutDemo(); window.setTitle("GridLayoutDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Die ersten zwei Parameter definieren die Anzahl der Zeilen (rows) und Spalten (columns) und legen das Format des Rasters (aber nicht deren Größe) fest. • Die optionalen Parameter drei und vier definieren einen festen horizontalen bzw. vertikalen Abstand zwischen den Zellen. 9.2.6 Grid Bag Layout Die Klasse GridBagLayout implementiert den flexibelsten Layout Algorithmus. Er ordnet Komponenten in einem tabellenförmigen Raster an, wobei die Spalten und Zeilen unterschiedliche Breite bzw. Höhe haben können. Einzelne Komponenten können sich auch über mehrere Zellen ausdehnen. Abbildung 9.15: GridBagLayoutDemo // gridbaglayout/GridBagLayoutDemo.java import import import import import import java.awt.Container; java.awt.GridBagLayout; java.awt.GridBagConstraints; java.awt.Insets; javax.swing.JFrame; javax.swing.JButton; public class GridBagLayoutDemo extends JFrame { public GridBagLayoutDemo() { JButton button; Container contentPane = getContentPane(); GridBagLayout gridbag = new GridBagLayout(); contentPane.setLayout(gridbag); 9.2. LAYOUT MANAGER GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; button = new JButton("Button 1"); c.weightx = 0.5; c.gridx = 0; c.gridy = 0; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("2"); c.gridx = 1; c.gridy = 0; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("Button 3"); c.gridx = 2; c.gridy = 0; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("Long-Named Button 4"); c.ipady = 40; //make this component tall c.weightx = 0.0; c.gridwidth = 3; c.gridx = 0; c.gridy = 1; gridbag.setConstraints(button, c); contentPane.add(button); button = new JButton("Button 5"); c.ipady = 0; //reset to default c.weighty = 1.0; //request any extra vertical space c.anchor = GridBagConstraints.SOUTH; //bottom of space c.insets = new Insets(10,0,0,0); //top padding c.gridx = 1; //aligned with button 2 c.gridwidth = 2; //2 columns wide c.gridy = 2; //third row gridbag.setConstraints(button, c); contentPane.add(button); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[]) { GridBagLayoutDemo window = new GridBagLayoutDemo(); window.setTitle("GridBagLayoutDemo"); window.pack(); window.setVisible(true); } } 125 126 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) Bemerkungen: • Für jede Komponente bestimmt ein GridBagConstraints Objekt die Layout-Parameter. • Die GridBagConstraints Instanzvariablen gridx und gridy definieren die Position der Komponente im Gitter. • Die Konstante GridBagConstraints.RELATIVE kann für die nächste Position rechts (bzw. unten) benutzt werden. • Die GridBagConstraints Instanzvariablen gridwidth und gridheight bestimmen die Anzahl Zellen, die von der Komponente belegt werden sollen. • Die GridBagConstraints Instanzvariable fill bestimmt, ob bzw. wie eine Komponente übrigen Platz aufnehmen soll. Es gibt hierfür die Konstanten NONE, HORIZONTAL, VERTICAL und BOTH. • Ein interner Abstand (internal padding) kann mit Hilfe der ipadx und ipady Instanzvariable definiert werden. • Ein externer Abstand (external padding) kann mit Hilfe der inset Instanzvariablen definiert werden. Der Wert ist ein Objekt der Klasse Insets. • Ein Insets Objekt repräsentiert den Rand eine Containers. Der Insets-Konstruktor erwartet die Parameter top, left, bottom, right. • Ist die Komponente kleiner als der verfügbare Platz, dann bestimmt die anchor Instanzvariable, wo die Komponente in verfügbaren Raum platziert wird. Zur Definition der Ausrichtung dienen die Konstanten NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST und NORTHWEST. • Mit Hilfe der weightx und weighty Variablen wird definiert, wie freier Platz auf die Spalten einer Zeile bzw. auf die Zeilen einer Spalte verteilt wird. Wichtig ist dabei das Verhältnis der Gewichte der einzelnen Komponenten zueinander und nicht der absolute Wert. Die Werte liegen typischerweise zwischen 0.0 und 1.0, wobei ein größerer Wert mehr Gewicht bedeutet. 9.2.7 Absolute Positionierung In einigen Spezialfällen ist es nicht sinnvoll, dass das Layout dynamisch erfolgt. In diesen Fällen kann man die Komponenten absolut platzieren. Abbildung 9.16: AbsoluteLayoutDemo // absolutelayout/AbsoluteLayoutDemo.java import import import import java.awt.Container; java.awt.Insets; javax.swing.JFrame; javax.swing.JButton; public class AbsoluteLayoutDemo extends JFrame { public AbsoluteLayoutDemo() 9.2. LAYOUT MANAGER 127 { JButton b1, b2, b3; Container contentPane = getContentPane(); contentPane.setLayout(null); b1 = new JButton("one"); contentPane.add(b1); b2 = new JButton("two"); contentPane.add(b2); b3 = new JButton("three"); contentPane.add(b3); Insets insets = contentPane.getInsets(); b1.setBounds(25 + insets.left, 5 + insets.top, 75, 20); b2.setBounds(55 + insets.left, 35 + insets.top, 75, 20); b3.setBounds(150 + insets.left, 15 + insets.top, 75, 30); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String args[]) { AbsoluteLayoutDemo window = new AbsoluteLayoutDemo(); Insets insets = window.getInsets(); window.setTitle("AbsoluteLayoutDemo"); window.setSize(250 + insets.left + insets.right, 90 + insets.top + insets.bottom); window.setVisible(true); } } Bemerkungen: • Die Position wird mit der Methode setBounds direkt als Eigenschaft der Komponenten festgelegt. 9.2.8 Implementation eines Layout-Manager Man kann relativ einfach neue Layout-Manager realisieren, indem man das LayoutManager oder das erweiterte LayoutManager2 Interface implementiert. Im LayoutManager Interface sind die folgenden Methoden festgelegt: • Die Methode addLayoutComponent() wird von der add() Methode eines Containers aufgerufen, wenn eine Komponente einem Container hinzugefügt wird. Mit Hilfe dieser Methode können Parameter an den Layout-Manager durchgereicht werden. Man beachte, dass der Container die enthaltenen Komponenten verwaltet und man unter Umständen auf eine eigene Verwaltung verzichten kann. • Die Methode removeLayoutComponent() wird von der Methode remove() oder der Methode removeAll() des Containers aufgerufen. • Die Methode preferredLayoutSize() bzw. minimumLayoutSize() wird aufgerufen, wenn ein anderes Objekt oder ein anderer Layout-Manager die natürliche bzw. minimale Größe eines Containers erfahren möchte. 128 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) • Die Methode layoutContainer() wird jedesmal aufgerufen, wenn sich die Größe des Containers verändert. In der Methode wird typischerweise die Position und Größe der im Container enthaltenen Komponenten neu berechnet. Bei der Berechnung ist ein interner Rand des Containers (insets) zu berücksichtigen. Der folgende Quelltext implementiert einen Layout-Manager, der die einzelnen Komponenten diagonal von der linken oberen Ecke zur rechten unteren Ecke anordnet. // diagonallayout/DiagonalLayout.java import import import import import import java.awt.LayoutManager; java.awt.Container; java.awt.Component; java.awt.Dimension; java.awt.Insets; java.util.Vector; public class DiagonalLayout implements LayoutManager { private int vgap; private Dimension preferred = new Dimension(); private Dimension minimum = new Dimension(); private Dimension maximum = new Dimension(); public DiagonalLayout() { this(5); } public DiagonalLayout(int v) { vgap = v; } /** * Required by the LayoutManager interface and ignored here since * we have no need to keep a list of components ourself. */ public void addLayoutComponent(String name, Component comp) { } /** * Required by the LayoutManager interface and ignored here since * we have no need to keep a list of components ourself. */ public void removeLayoutComponent(Component comp) { } /** * Compute the preferred and minimum width and height based on the * parameters of the components and a layout where a subsequent * component start horizontally in the middle of the previous * component. Add a vgap between components. */ private void setSizes(Container parent) { 9.2. LAYOUT MANAGER 129 int nComps = parent.getComponentCount(); Insets insets = parent.getInsets(); preferred.width = insets.left + insets.right; preferred.height = insets.top + insets.bottom; minimum.width = insets.left + insets.right; minimum.height = insets.top + insets.bottom; for (int i = 0; i < nComps; i++) { Component c = parent.getComponent(i); if (c.isVisible()) { Dimension d = c.getPreferredSize(); preferred.width += (i == 0) ? d.width : d.width / 2; preferred.height += (i == 0) ? d.height : d.height + vgap; minimum.width = Math.max(c.getMinimumSize().width, minimum.width); minimum.height = preferred.height; } } maximum.width = parent.getWidth(); maximum.height = parent.getHeight(); } /** * Required by LayoutManager. Simply calculate the sizes and * return the preferred size. */ public Dimension preferredLayoutSize(Container parent) { setSizes(parent); return preferred; } /** * Required by LayoutManager. Simply calculate the sizes and * return the minimum size. */ public Dimension minimumLayoutSize(Container parent) { setSizes(parent); return minimum; } /** * Required by LayoutManager2. Simply calculate the sizes and * return the maximum size. */ public Dimension maximumLayoutSize(Container parent) { setSizes(parent); 130 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) return maximum; } /** * Required by LayoutManager. Arrange the components in diagonal * if possible. Make sure components do not move outside of the * container on the top or left. */ public void layoutContainer(Container parent) { int nComps = parent.getComponentCount(); Insets insets = parent.getInsets(); int x = insets.right, y = insets.top; int xFudge = 0, yFudge = 0; Dimension previous = new Dimension(0, 0); setSizes(parent); if (maximum.width != preferred.width) { xFudge = (maximum.width - preferred.width)/(nComps - 1); } if (maximum.height != preferred.height) { yFudge = (maximum.height - preferred.height)/(nComps - 1); } for (int i = 0 ; i < nComps ; i++) { Component c = parent.getComponent(i); if (c.isVisible()) { Dimension d = c.getPreferredSize(); if (i > 0) { x += Math.max(previous.width / 2 + xFudge, 0); y += Math.max(previous.height + vgap + yFudge, 0); } c.setBounds(x, y, d.width, d.height); previous = d; } } } public String toString() { return getClass().getName() + "[vgap=" + vgap + "]"; } } Bemerkungen: • Die Implementierung verzichtet darauf, die enthaltenen Komponenten selbst zu verwalten. Stattdessen wird mit Hilfe von getComponentCount() und getComponent() auf die Verwaltung der Komponenten im Container zurückgegriffen. • Generell werden nur die Komponenten betrachtet, die auch aktuell sichtbar sind. • Die Anpassung der Größe der Komponenten erfolgt mit Hilfe der Methode setBounds, die jede Komponente implementiert. Eine kleine Demonstration des neuen Layout-Managers: 9.2. LAYOUT MANAGER // diagonallayout/DiagonalLayoutDemo.java import import import import import import java.awt.Container; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JFrame; javax.swing.JButton; DiagonalLayout; public class DiagonalLayoutDemo extends JFrame { public DiagonalLayoutDemo() { Container contentPane = getContentPane(); contentPane.setLayout(new DiagonalLayout(0)); for (int i = 0; i < 5; i++) { JButton b = new JButton("Button " + i); b.addActionListener(new ButtonActionListener()); contentPane.add(b); } setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } class ButtonActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { // JButton b = (JButton) e.getSource(); // b.setText("[" + b.getText() + "]"); } } public static void main(String args[]) { DiagonalLayoutDemo window = new DiagonalLayoutDemo(); window.setTitle("DiagonalLayoutDemo"); window.pack(); window.setVisible(true); } } 131 132 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) 9.3 Ränder Oftmals ist es sinnvoll, einen Rand um eine Komponente zu zeichnen. Das Interface Border definiert die Schnittstelle für einen Rand. Die abstrakte Klasse AbstractBorder implementiert diese Schnittstelle und dient als Basisklasse für verschiedene Arten von Rändern. Applikationen erzeugen normalerweise konkrete Ränder durch den Aufruf von entsprechenden Methoden der Klasse BorderFactory. «interface» Border EmptyBorder AbstractBorder MatteBorder LineBorder EtchedBorder Object BevelBorder SoftBevelBorder BorderFactory TitledBorder CompoundBorder Abbildung 9.17: Swing Border Klassenhierarchie 9.3.1 Einfache Ränder Einfache Ränder definieren einen Rahmen um eine Komponente, der unterschiedlich hervorgehoben oder begrenzt sein kann. Abbildung 9.18: BorderSimpleDemo // bordersimple/BorderSimpleDemo.java import java.awt.Container; 9.3. RÄNDER import import import import import import import import import import 133 java.awt.Color; java.awt.Dimension; java.awt.BorderLayout; javax.swing.BorderFactory; javax.swing.border.Border; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JFrame; javax.swing.Box; javax.swing.BoxLayout; public class BorderSimpleDemo extends JFrame { public BorderSimpleDemo() { Border border; Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); border = BorderFactory.createLineBorder(Color.black); addLabel(border, "line border"); border = BorderFactory.createEtchedBorder(); addLabel(border, "etched border"); border = BorderFactory.createRaisedBevelBorder(); addLabel(border, "raised bevel border"); border = BorderFactory.createLoweredBevelBorder(); addLabel(border, "lowered bevel border"); border = BorderFactory.createEmptyBorder(); addLabel(border, "empty border"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } void addLabel(Border border, String description) { JPanel panel = new JPanel(); JLabel label = new JLabel(description, JLabel.CENTER); panel.add(label); panel.setBorder(border); getContentPane().add(Box.createRigidArea(new Dimension(0, 10))); getContentPane().add(panel); } public static void main(String[] args) { JFrame window = new BorderSimpleDemo(); window.setTitle("BorderSimpleDemo"); window.pack(); window.setVisible(true); 134 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) } } Bemerkungen: • Jede Swing-Komponente, die von JComponent abgeleitet ist, kann einen Rand besitzen. Ränder selbst sind jedoch keine eigenständigen Swing-Komponenten. • Die setBorder Methode der Klasse JComponent erlaubt es, den Rand zu definieren. 9.3.2 Ränder mit Titeln Ränder lassen sich mit Titeln zu versehen, um auf diese Weise zusammenhängende Komponenten unter einem Begriff zusammenzufassen. Abbildung 9.19: BorderTitleDemo // bordertitle/BorderTitleDemo.java import import import import import import import import import import java.awt.Container; java.awt.Color; java.awt.BorderLayout; javax.swing.BorderFactory; javax.swing.border.Border; javax.swing.border.TitledBorder; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JFrame; javax.swing.BoxLayout; public class BorderTitleDemo extends JFrame { public BorderTitleDemo() { Border border; TitledBorder titled; 9.3. RÄNDER 135 Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); titled = BorderFactory.createTitledBorder("title"); addLabel(titled, "default titled border", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION); border = BorderFactory.createLineBorder(Color.black); titled = BorderFactory.createTitledBorder(border, "title"); addLabel(titled, "titled line border", TitledBorder.CENTER, TitledBorder.DEFAULT_POSITION); border = BorderFactory.createEtchedBorder(); titled = BorderFactory.createTitledBorder(border, "title"); addLabel(titled, "titled etched border", TitledBorder.RIGHT, TitledBorder.DEFAULT_POSITION); border = BorderFactory.createLoweredBevelBorder(); titled = BorderFactory.createTitledBorder(border, "title"); addLabel(titled, "titled lowered bevel border", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.ABOVE_TOP); border = BorderFactory.createEmptyBorder(); titled = BorderFactory.createTitledBorder(border, "title"); addLabel(titled, "titled empty border", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.BOTTOM); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } void addLabel(TitledBorder border, String description, int justification, int position) { border.setTitleJustification(justification); border.setTitlePosition(position); JPanel panel = new JPanel(); JLabel label = new JLabel(description, JLabel.CENTER); panel.add(label); panel.setBorder(border); getContentPane().add(panel); } public static void main(String[] args) { JFrame window = new BorderTitleDemo(); window.setTitle("BorderTitleDemo"); window.pack(); window.setVisible(true); } 136 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) } Bemerkungen: • Ein TitledBorder benutzt einen beliebigen Border um darauf einen Titel anzuzeigen. • Was passiert wohl, wenn der zugrundeliegende Border schon ein TitledBorder ist? • Für den Titel lässt sich die Ausrichtung und die Position festlegen. 9.3.3 Zusammengesetzte Ränder Komplexe zusammengesetzte Ränder lassen sich aus einfacheren Ränder zusammensetzen. Abbildung 9.20: BorderCompoundDemo // bordercompound/BorderCompoundDemo.java import import import import import import import import import import import import java.awt.Container; java.awt.Dimension; java.awt.Color; java.awt.BorderLayout; javax.swing.BorderFactory; javax.swing.border.Border; javax.swing.border.TitledBorder; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JFrame; javax.swing.Box; javax.swing.BoxLayout; public class BorderCompoundDemo extends JFrame { public BorderCompoundDemo() { Container contentPane = getContentPane(); contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS)); Border raisedbevel = BorderFactory.createRaisedBevelBorder(); addLabel(raisedbevel, "raised bevel border"); 9.3. RÄNDER 137 Border loweredbevel = BorderFactory.createLoweredBevelBorder(); addLabel(loweredbevel, "lowered bevel border"); Border compound = BorderFactory.createCompoundBorder(raisedbevel, loweredbevel); addLabel(compound, "compound border (two bevels)"); Border redline = BorderFactory.createLineBorder(Color.red); compound = BorderFactory.createCompoundBorder(redline, compound); addLabel(compound, "compound border (add a red outline)"); TitledBorder titled = BorderFactory.createTitledBorder(compound, "title"); addLabel(titled, "compound border (add a title)"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } void addLabel(Border border, String description) { JPanel panel = new JPanel(); JLabel label = new JLabel(description, JLabel.CENTER); panel.add(label); panel.setBorder(border); getContentPane().add(Box.createRigidArea(new Dimension(0, 10))); getContentPane().add(panel); } public static void main(String[] args) { JFrame window = new BorderCompoundDemo(); window.setTitle("BorderCompoundDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Einige der Swing-Komponenten benutzen Border um sich selbst darzustellen. • Ein Aufruf der setBorder Methode kann bei diesen Klassen u.U. nicht den gewünschten Effekt liefern. 138 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) 9.4 Menüs Menüs sind ein platzsparender Mechanismus, mit dem ein Benutzer eine von vielen möglichen Funktionen auswählen kann. Abbildung 9.21: MenuDemo // menu/MenuDemo.java import import import import import import import import import import import import import java.awt.event.KeyEvent; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.Box; javax.swing.JFrame; javax.swing.JMenu; javax.swing.JMenuItem; javax.swing.JCheckBoxMenuItem; javax.swing.JRadioButtonMenuItem; javax.swing.ButtonGroup; javax.swing.JMenuBar; javax.swing.KeyStroke; javax.swing.ImageIcon; public class MenuDemo extends JFrame { public MenuDemo() { JMenuBar menuBar; JMenu menu, submenu; JMenuItem menuItem; JCheckBoxMenuItem cbMenuItem; JRadioButtonMenuItem rbMenuItem; menuBar = new JMenuBar(); setJMenuBar(menuBar); menu = new JMenu("Menu"); 9.4. MENÜS 139 menu.setMnemonic(KeyEvent.VK_M); menuBar.add(menu); menuItem = new JMenuItem("A text-only menu item"); menuItem.setMnemonic(KeyEvent.VK_T); menuItem.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_1, ActionEvent.ALT_MASK)); menu.add(menuItem); menuItem = new JMenuItem("Both text and icon", new ImageIcon("middle.gif")); menuItem.setMnemonic(KeyEvent.VK_B); menu.add(menuItem); menuItem = new JMenuItem(new ImageIcon("middle.gif")); menuItem.setMnemonic(KeyEvent.VK_D); menu.add(menuItem); menu.addSeparator(); ButtonGroup group = new ButtonGroup(); rbMenuItem = new JRadioButtonMenuItem("A radio button menu item"); rbMenuItem.setSelected(true); rbMenuItem.setMnemonic(KeyEvent.VK_R); group.add(rbMenuItem); menu.add(rbMenuItem); rbMenuItem = new JRadioButtonMenuItem("Another one"); rbMenuItem.setMnemonic(KeyEvent.VK_O); group.add(rbMenuItem); menu.add(rbMenuItem); menu.addSeparator(); cbMenuItem = new JCheckBoxMenuItem("A check box menu item"); cbMenuItem.setMnemonic(KeyEvent.VK_C); menu.add(cbMenuItem); cbMenuItem = new JCheckBoxMenuItem("Another one"); cbMenuItem.setMnemonic(KeyEvent.VK_H); menu.add(cbMenuItem); menu.addSeparator(); submenu = new JMenu("A submenu"); submenu.setMnemonic(KeyEvent.VK_S); menuItem = new JMenuItem("An item in the submenu"); menuItem.setAccelerator(KeyStroke.getKeyStroke( KeyEvent.VK_2, ActionEvent.ALT_MASK)); submenu.add(menuItem); menuItem = new JMenuItem("Another item"); submenu.add(menuItem); 140 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) menu.add(submenu); menu.addSeparator(); menuItem = new JMenuItem("Exit"); menuItem.setMnemonic(KeyEvent.VK_E); menuItem.addActionListener(new ExitListener()); menu.add(menuItem); menu = new JMenu("AnotherMenu"); menu.setMnemonic(KeyEvent.VK_A); menu.setEnabled(false); menuBar.add(menu); menu = new JMenu("Help"); menu.setMnemonic(KeyEvent.VK_H); menuBar.add(Box.createHorizontalGlue()); menuBar.add(menu); menuItem = new JMenuItem("About"); menuItem.setEnabled(false); menu.add(menuItem); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } class ExitListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.exit(0); } } public static void main(String[] args) { MenuDemo window = new MenuDemo(); window.setTitle("MenuDemo"); window.setSize(450, 260); window.setVisible(true); } } Bemerkungen: • Ein Objekt der Klasse JMenuBar wird mit Hilfe der setJMenuBar Methode einem JFrame zugeordnet. • Ein Objekt der Klasse JMenuBar enthält ein oder mehrere Objekte der Klasse JMenu, die selbst Objekte der Klassen JMenuItem, JRadioButtonMenuItem, JCheckBoxMenuItem oder JMenu enthalten. • Die Methode addSeparator erzeugt im Menü einen Trennstrich. • Alle Elemente eines Menüs können einzeln mit der Methode setEnabled gesperrt werden. • Auf die einzelnen Menüpunkte kann durch die Definition von ActionListener reagiert werden. 141 9.4. MENÜS 9.4.1 Toolbars Häufig gebrauchte Menüpunkte können in sogenannten Toolbars untergebracht werden. Abbildung 9.22: ToolBarDemo // toolbar/ToolBarDemo.java import import import import import import import import import import import java.awt.Dimension; java.awt.BorderLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JToolBar; javax.swing.JButton; javax.swing.ImageIcon; javax.swing.JFrame; javax.swing.JTextArea; javax.swing.JScrollPane; javax.swing.JPanel; public class ToolBarDemo extends JFrame { protected JTextArea textArea; public ToolBarDemo() { JToolBar toolBar = new JToolBar(); addButtons(toolBar); textArea = new JTextArea(5, 30); JScrollPane scrollPane = new JScrollPane(textArea); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); contentPane.setPreferredSize(new Dimension(400, 100)); contentPane.add(toolBar, BorderLayout.NORTH); contentPane.add(scrollPane, BorderLayout.CENTER); setContentPane(contentPane); } protected void addButtons(JToolBar toolBar) { JButton button = null; button = new JButton(new ImageIcon("left.gif")); button.setToolTipText("left button"); button.addActionListener(new ActionListener() { 142 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) public void actionPerformed(ActionEvent e) { displayResult("Action for left button"); } }); toolBar.add(button); button = new JButton(new ImageIcon("middle.gif")); button.setToolTipText("middle button"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { displayResult("Action for middle button"); } }); toolBar.add(button); button = new JButton(new ImageIcon("right.gif")); button.setToolTipText("right button"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { displayResult("Action for right button"); } }); toolBar.add(button); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } protected void displayResult(String actionDescription) { textArea.append(actionDescription + "\n"); } public static void main(String[] args) { ToolBarDemo window = new ToolBarDemo(); window.setTitle("ToolBarDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Die Elemente einer JToolBar werden in einer Zeile oder Spalte angeordnet. • Die JToolBar kann vom Benutzer an die verschiedenen Kanten des Containers geschoben werden oder ganz abgenommen werden. • Damit das Verschieben funktioniert, muss der Container den BorderLayout Algorithmus verwenden und sich im Zentrum befinden. • Mit Hilfe der setFloatable Methode kann das Verschieben eines Objekts der Klasse JToolBar verhindert werden. • Die Methode addSeparator erzeugt einen kleinen Abstand zwischen Elementen einer JToolBar. 143 9.4. MENÜS 9.4.2 Actions Wenn mehrere JButtons bzw. JMenuItems dieselbe Aktion ausführen, dann ist die Implementation einer gemeinsamen Aktion (actions) sinnvoll, um die Konsistenz der Oberfläche zu gewährleisten und redundanten Quelltext zu vermeiden. <<interface>> EventListener <<interface>> ActionListener <<interface>> Action CutAction Object AbstractAction TextAction CopyAction PasteAction Abbildung 9.23: Einige Swing-Klassen für Aktionen Bemerkungen: • Ein Objekt, das das Action Interface implementiert, kapselt den Namen und das Icon im Menü bzw. Toolbarbutton sowie den Zustand der Action. • Änderungen des Zustands der Action werden automatisch an die eingetragenen Menü bzw. Toolbarbutton propagiert. • Es gibt spezielle add() Methoden für JToolBar und JMenu Objekte, die eine Action entgegennehmen und einen passenden JButton bzw. JMenuItem erzeugen. // action/ActionDemo.java import import import import import import import import import import import import import import java.awt.Dimension; java.awt.BorderLayout; java.awt.event.ActionEvent; java.awt.event.ItemEvent; java.awt.event.ItemListener; javax.swing.AbstractAction; javax.swing.Action; javax.swing.JToolBar; javax.swing.JButton; javax.swing.ImageIcon; javax.swing.JMenuItem; javax.swing.JCheckBoxMenuItem; javax.swing.JMenu; javax.swing.JMenuBar; 144 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) import import import import javax.swing.JFrame; javax.swing.JTextArea; javax.swing.JScrollPane; javax.swing.JPanel; public class ActionDemo extends JFrame { protected JTextArea textArea; protected Action leftAction; protected Action middleAction; protected Action rightAction; public ActionDemo() { JToolBar toolBar = new JToolBar(); toolBar.setFloatable(false); JMenu mainMenu = new JMenu("Menu"); createButtons(toolBar, mainMenu); JMenu actionMenu = new JMenu("Action State"); createStateMenu(actionMenu); textArea = new JTextArea(5, 30); JScrollPane scrollPane = new JScrollPane(textArea); JPanel contentPane = new JPanel(); contentPane.setLayout(new BorderLayout()); contentPane.setPreferredSize(new Dimension(400, 150)); contentPane.add(toolBar, BorderLayout.NORTH); contentPane.add(scrollPane, BorderLayout.CENTER); setContentPane(contentPane); JMenuBar mb = new JMenuBar(); mb.add(mainMenu); mb.add(actionMenu); setJMenuBar(mb); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } protected void createButtons(JToolBar toolBar, JMenu mainMenu) { JButton button = null; JMenuItem menuItem = null; leftAction = new AbstractAction("Left", new ImageIcon("left.gif")) { public void actionPerformed(ActionEvent e) { displayResult("Left button/menu action", e); } }; button = toolBar.add(leftAction); button.setText(""); 9.4. MENÜS button.setToolTipText("This is the left button"); menuItem = mainMenu.add(leftAction); menuItem.setIcon(null); middleAction = new AbstractAction("Middle", new ImageIcon("middle.gif")) { public void actionPerformed(ActionEvent e) { displayResult("Middle button/menu action", e); } }; button = toolBar.add(middleAction); button.setText(""); button.setToolTipText("This is the middle button"); menuItem = mainMenu.add(middleAction); menuItem.setIcon(null); rightAction = new AbstractAction("Right", new ImageIcon("right.gif")) { public void actionPerformed(ActionEvent e) { displayResult("Right button/menu action", e); } }; button = toolBar.add(rightAction); button.setText(""); button.setToolTipText("This is the right button"); menuItem = mainMenu.add(rightAction); menuItem.setIcon(null); } protected void createStateMenu(JMenu actionMenu) { JCheckBoxMenuItem cbmi = null; cbmi = new JCheckBoxMenuItem("Left action enabled"); cbmi.setSelected(true); cbmi.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean selected = (e.getStateChange() == ItemEvent.SELECTED); leftAction.setEnabled(selected); } }); actionMenu.add(cbmi); cbmi = new JCheckBoxMenuItem("Middle action enabled"); cbmi.setSelected(true); cbmi.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean selected = (e.getStateChange() == ItemEvent.SELECTED); middleAction.setEnabled(selected); } }); actionMenu.add(cbmi); 145 146 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) cbmi = new JCheckBoxMenuItem("Right action enabled"); cbmi.setSelected(true); cbmi.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { boolean selected = (e.getStateChange() == ItemEvent.SELECTED); rightAction.setEnabled(selected); } }); actionMenu.add(cbmi); } protected void displayResult(String description, ActionEvent e) { textArea.append("Action description: " + description + "\n" + "Event source: " + e.getSource() + "\n"); } public static void main(String[] args) { ActionDemo window = new ActionDemo(); window.setTitle("ActionDemo"); window.pack(); window.setVisible(true); } } 147 9.5. SCROLLING 9.5 Scrolling Mit Hilfe der Klasse JScrollPane und des Interfaces Scrollable lassen sich verschiebbare Ausschnitte einer Komponente darstellen. Der Ausschnitt wird durch ein Objekt der Klasse JViewport dargestellt. Abbildung 9.24: Swing JScrollPane Funktionsmodell Bemerkungen: • Eine JScrollPane nimmt eine Komponente auf, die das Interface Scrollable implementieren muss (JList, JTextComponent, JTree, JTable). • An der rechten und unteren Seite des sichtbaren Ausschnitts der Komponente können ein vertikale bzw. ein horizontaler Rollbalken dargestellt werden. • An der linken und oberen Seite des sichtbaren Ausschnitts der Komponente können Komponenten als Zeilen- bzw. Spalterüberschriften dargestellt werden. Mit den Methoden setColumnHeaderView und setRowHeaderView können die Überschriften gesetzt werden. • Die sich ergebenden Ecken können ebenfalls mit Komponenten belegt werden. Abbildung 9.25: ScrollDemo 148 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) // scroll/ScrollDemo.java import import import import import import import java.awt.Toolkit; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JFrame; javax.swing.JTextArea; javax.swing.JScrollPane; public class ScrollDemo extends JFrame { private JScrollPane scrollPane; public ScrollDemo() { JTextArea textArea = new JTextArea(5, 30); scrollPane = new JScrollPane(textArea); getContentPane().add(scrollPane); int policy = JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS; scrollPane.setHorizontalScrollBarPolicy(policy); JButton button = new JButton(); scrollPane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, button); button.addActionListener(new ButtonListener()); for (int i = 0; i < 8; i++) { textArea.append("Line " + i + "\n"); } setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent event) { Toolkit.getDefaultToolkit().beep(); } } public static void main(String[] args) { ScrollDemo window = new ScrollDemo(); window.setTitle("ScrollDemo"); window.pack(); window.setVisible(true); } } 149 9.6. SPLITTING 9.6 Splitting Mit Hilfe der Klasse JSplitPane können zwei Komponenten nebeneinander oder übereinander dargestellt werden, wobei die Aufteilung des zur Verfügung stehenden Platzes durch den Benutzer manipuliert werden kann. Abbildung 9.26: SplitDemo // split/SplitDemo.java import import import import import import import import import java.awt.Dimension; javax.swing.JLabel; javax.swing.JFrame; javax.swing.JTextArea; javax.swing.JList; javax.swing.DefaultListModel; javax.swing.JScrollPane; javax.swing.JOptionPane; javax.swing.JSplitPane; public class SplitDemo extends JFrame { public SplitDemo() { DefaultListModel listModel = new DefaultListModel(); JList listArea = new JList(listModel); JTextArea textArea = new JTextArea(5, 30); JScrollPane textScrollPane = new JScrollPane(textArea); JScrollPane listScrollPane = new JScrollPane(listArea); JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, listScrollPane, textScrollPane); splitPane.setOneTouchExpandable(true); splitPane.setDividerLocation(0.25); JLabel label = new JLabel("Another useless label"); JSplitPane topSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, splitPane, label); getContentPane().add(topSplitPane); 150 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) Dimension minimumSize = new Dimension(100, 50); listScrollPane.setMinimumSize(minimumSize); textScrollPane.setMinimumSize(minimumSize); for (int i = 0; i < 10; i++) { textArea.append("Text line " + i + "\n"); listModel.addElement("List item " + i); } setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { SplitDemo window = new SplitDemo(); window.setTitle("SplitDemo"); window.pack(); window.setVisible(true); } } • Das explizite Setzen von setOneTouchExpandable stellt sicher, dass die kleinen Knöpfe zum Öffnen/Schließen der Teile sichtbar sind. Wenn man diesen Parameter nicht explizit setzt, dann hängt das Erscheinen vom verwendeten LookAndFeel ab. • Mit setDividerLocation kann die Position der Trennlinie definiert werden. Ein Wert zwischen 0 und 1 definiert die relative Größe der linken/oberen Hälfte während eine ganze Zahl die Größe der linken/oberen Hälfte in Pixeln festlegt. • Das Setzen der minimalen Größe der Komponenten eines JSplitPane bewirkt, dass die einzelnen Teile nicht beliebig klein werden können. • Bei der Konstruktion der JList Komponente wird eine Trennung von Model und View benutzt. Die Instanz der Klasse DefaultListModel enthält die eigentlichen Daten und realisiert das Model während die Instanz der Klasse JList den entsprechenden View realisiert. 151 9.7. TEXT KOMPONENTEN 9.7 Text Komponenten Die Swing-Klassen stellen verschiedenen Komponenten zur Darstellung und Manipulation von Texten bereit. Diese Komponenten trennen den eigentlichen Text (ein Dokument) von seiner Darstellung. 9.7.1 Dokumente Ein Dokument wird durch eine Klasse realisiert, die das Interface Document realisiert. Jeder Teilabschnitt eines Dokuments besteht aus einem Element. Eine Erweiterung des Interfaces Document existiert für Dokumente, die über eine logische Struktur verfügen und in denen einzelne Textabschnitte verschiedene Attribute besitzen können. «interface» «interface» Document StyledDocument «interface» Element DefaultStyledDocument Object HTMLDocument AbstractDocument PlainDocument «interface» «interface» EventListener DocumentListener Abbildung 9.27: Swing Klassen und Interfaces für Dokumente • Ein PlainDocument wird für einfache Textfelder benutzt, die den gesamten Text in demselben Zeichensatz darstellen. • Jedem Dokument kann mit addDocumentListener ein DocumentListener zugeordnet werden. • Das DocumentListener Interface erfordert die Implementation der Methoden insertUpdate, removeUpdate und changedUpdate. • Instanzen der Klasse PlainDocument erzeugen lediglich Events für das Einfügen und Löschen. 152 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) 9.7.2 Ereignisse von Dokumenten Abbildung 9.28: DocumentEventDemo // documentevent/DocumentEventDemo.java import import import import import import import import import import import import java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.Dimension; javax.swing.BoxLayout; javax.swing.JPanel; javax.swing.JFrame; javax.swing.JScrollPane; javax.swing.JTextField; javax.swing.JTextArea; javax.swing.text.Document; javax.swing.event.DocumentEvent; javax.swing.event.DocumentListener; public class DocumentEventDemo extends JFrame { JTextField textField; JTextArea displayArea; public DocumentEventDemo() { textField = new JTextField(40); textField.addActionListener(new MyTextActionListener()); textField.getDocument().addDocumentListener(new MyDocumentListener()); displayArea = new JTextArea(10,40); displayArea.setEditable(false); JScrollPane displayScrollPane = new JScrollPane(displayArea); JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.add(textField); panel.add(displayScrollPane); setContentPane(panel); 9.7. TEXT KOMPONENTEN 153 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { updateLog(e, "inserted into"); } public void removeUpdate(DocumentEvent e) { updateLog(e, "removed from"); } public void changedUpdate(DocumentEvent e) { } public void updateLog(DocumentEvent e, String action) { Document doc = (Document) e.getDocument(); int changeLength = e.getLength(); displayArea.append(changeLength + " character" + ((changeLength == 1) ? " " : "s ") + action + " text field; text length = " + doc.getLength() + "\n"); } } class MyTextActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { displayArea.append("Contents: " + textField.getText() + "\n"); displayArea.append("Selected: " + textField.getSelectedText() + "\n"); textField.select(0, 0); } } public static void main(String args[]) { DocumentEventDemo window = new DocumentEventDemo(); window.setTitle("DocumentEventDemo"); window.pack(); window.setVisible(true); } } 9.7.3 Dokument Editor Mit Hilfe der Klasse JEditorPane bekommt man einen kleinen Editor, mit dem man strukturierte Dokumente anzeigen und edieren kann. 154 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) Abbildung 9.29: EditorPaneDemo // editorpane/EditorPaneDemo.java import import import import import import import import import import import import import java.awt.Dimension; java.awt.BorderLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JFrame; javax.swing.JTextField; javax.swing.JPanel; javax.swing.JEditorPane; javax.swing.JScrollPane; javax.swing.JOptionPane; java.net.URL; java.net.MalformedURLException; java.io.IOException; public class EditorPaneDemo extends JFrame { private JEditorPane editorPane; private JTextField textField; public EditorPaneDemo(String start) { textField = new JTextField(start); textField.addActionListener(new MyTextActionListener()); editorPane = new JEditorPane(); editorPane.setEditable(false); JScrollPane editorScrollPane = new JScrollPane(editorPane); editorScrollPane.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); editorScrollPane.setPreferredSize(new Dimension(250, 145)); editorScrollPane.setMinimumSize(new Dimension(10, 10)); 9.7. TEXT KOMPONENTEN 155 JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add(textField, BorderLayout.NORTH); panel.add(editorScrollPane, BorderLayout.CENTER); setContentPane(panel); showUrl(start); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void showUrl(String s) { URL url; try { url = new URL(s); } catch (MalformedURLException e) { showErrorMsg("malformed URL \"" + s + "\""); return; } try { editorPane.setPage(url); } catch (IOException e) { showErrorMsg("IO exception while reading URL \"" + s + "\""); } } public void showErrorMsg(String s) { JOptionPane.showMessageDialog(editorPane, s, "Error", JOptionPane.ERROR_MESSAGE); } class MyTextActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { showUrl(textField.getText()); } } public static void main(String[] args) { final String start = "http://www.vorlesungen.uni-osnabrueck.de/informatik/c02/"; if (args.length == 0) { JFrame window = new EditorPaneDemo(start); window.setTitle("EditorPaneDemo"); window.pack(); window.setVisible(true); } for (int i = 0; i < args.length; i ++) { JFrame window = new EditorPaneDemo(args[i]); window.setTitle("EditorPaneDemo"); window.pack(); 156 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) window.setVisible(true); } } } Bemerkungen: • Das Programm nutzt die Möglichkeit, einer Instanz der Klasse JEditorPane mit Hilfe der setPage Methode einen URL zu geben. Das JEditorPane holt sich darauf das HTML-Dokument und erzeugt ein neues HTMLDocument. • Offenbar ist die Implementation sogar in der Lage, automatisch weitere eingebettete Dokumente (z.B. Bilder) zu laden. 9.7.4 Edieren von Dokumenten Das folgende etwas längere Beispiel demonstriert, wie ein einfacher Dokumenten-Editor realisiert werden kann. Abbildung 9.30: TextComponentDemo // textcomponent/TextComponentDemo.java import import import import import import import import import import import import import import import import import import java.util.Hashtable; java.awt.Color; java.awt.Insets; java.awt.Dimension; java.awt.Event; java.awt.BorderLayout; java.awt.event.KeyEvent; java.awt.event.ActionEvent; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JScrollPane; javax.swing.JMenu; javax.swing.JMenuBar; javax.swing.JTextPane; javax.swing.KeyStroke; javax.swing.Action; javax.swing.AbstractAction; javax.swing.text.SimpleAttributeSet; 9.7. TEXT KOMPONENTEN import import import import import import import import import import import import import javax.swing.text.StyleConstants; javax.swing.text.StyledDocument; javax.swing.text.StyledEditorKit; javax.swing.text.DefaultEditorKit; javax.swing.text.DefaultStyledDocument; javax.swing.text.Keymap; javax.swing.text.JTextComponent; javax.swing.text.BadLocationException; javax.swing.event.UndoableEditEvent; javax.swing.event.UndoableEditListener; javax.swing.undo.UndoManager; javax.swing.undo.CannotUndoException; javax.swing.undo.CannotRedoException; public class TextComponentDemo extends JFrame { JTextPane textPane; StyledDocument doc; private final String newline = "\n"; private Hashtable actions; protected UndoAction undoAction; protected RedoAction redoAction; protected UndoManager undo = new UndoManager(); public TextComponentDemo() { doc = new DefaultStyledDocument(); textPane = new JTextPane(doc); textPane.setCaretPosition(0); textPane.setMargin(new Insets(5,5,5,5)); JScrollPane scrollPane = new JScrollPane(textPane); scrollPane.setPreferredSize(new Dimension(600, 200)); JPanel contentPane = new JPanel(new BorderLayout()); contentPane.add(scrollPane, BorderLayout.CENTER); setContentPane(contentPane); createActionTable(textPane); JMenu editMenu = createEditMenu(); JMenu styleMenu = createStyleMenu(); JMenuBar mb = new JMenuBar(); mb.add(editMenu); mb.add(styleMenu); setJMenuBar(mb); addKeymapBindings(); initDocument(); doc.addUndoableEditListener(new UndoableListener()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } protected class UndoableListener implements UndoableEditListener 157 158 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) { public void undoableEditHappened(UndoableEditEvent e) { undo.addEdit(e.getEdit()); undoAction.updateUndoState(); redoAction.updateRedoState(); } } protected void addKeymapBindings() { Keymap keymap = JTextComponent.addKeymap("MyEmacsBindings", textPane.getKeymap()); Action action = getActionByName(DefaultEditorKit.backwardAction); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); action = getActionByName(DefaultEditorKit.forwardAction); key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); action = getActionByName(DefaultEditorKit.upAction); key = KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); action = getActionByName(DefaultEditorKit.downAction); key = KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); textPane.setKeymap(keymap); } protected JMenu createEditMenu() { JMenu menu = new JMenu("Edit"); undoAction = new UndoAction(); menu.add(undoAction); redoAction = new RedoAction(); menu.add(redoAction); menu.addSeparator(); menu.add(getActionByName(DefaultEditorKit.cutAction)); menu.add(getActionByName(DefaultEditorKit.copyAction)); menu.add(getActionByName(DefaultEditorKit.pasteAction)); menu.addSeparator(); menu.add(getActionByName(DefaultEditorKit.selectAllAction)); return menu; } 9.7. TEXT KOMPONENTEN 159 protected JMenu createStyleMenu() { JMenu menu = new JMenu("Style"); Action action = new StyledEditorKit.BoldAction(); action.putValue(Action.NAME, "Bold"); menu.add(action); action = new StyledEditorKit.ItalicAction(); action.putValue(Action.NAME, "Italic"); menu.add(action); action = new StyledEditorKit.UnderlineAction(); action.putValue(Action.NAME, "Underline"); menu.add(action); menu.addSeparator(); menu.add(new StyledEditorKit.FontSizeAction("12", 12)); menu.add(new StyledEditorKit.FontSizeAction("14", 14)); menu.add(new StyledEditorKit.FontSizeAction("18", 18)); menu.addSeparator(); menu.add(new StyledEditorKit.FontFamilyAction("Serif", "Serif")); menu.add(new StyledEditorKit.FontFamilyAction("SansSerif", "SansSerif")); menu.addSeparator(); menu.add(new menu.add(new menu.add(new menu.add(new StyledEditorKit.ForegroundAction("Red", Color.red)); StyledEditorKit.ForegroundAction("Green", Color.green)); StyledEditorKit.ForegroundAction("Blue", Color.blue)); StyledEditorKit.ForegroundAction("Black", Color.black)); return menu; } protected void initDocument() { String initString[] = { "Use the mouse to place the caret.", "Use the edit menu to cut, copy, paste, and select text.", "Also to undo and redo changes.", "Use the style menu to change the style of the text.", "Use these emacs key bindings to move the caret:", "ctrl-f, ctrl-b, ctrl-n, ctrl-p." }; SimpleAttributeSet[] attrs = initAttributes(initString.length); try { for (int i = 0; i < initString.length; i ++) { doc.insertString(doc.getLength(), initString[i] + newline, attrs[i]); } } catch (BadLocationException ble) { System.err.println("Couldn’t insert initial text."); 160 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) } } protected SimpleAttributeSet[] initAttributes(int length) { SimpleAttributeSet[] attrs = new SimpleAttributeSet[length]; attrs[0] = new SimpleAttributeSet(); StyleConstants.setFontFamily(attrs[0], "SansSerif"); StyleConstants.setFontSize(attrs[0], 16); attrs[1] = new SimpleAttributeSet(attrs[0]); StyleConstants.setBold(attrs[1], true); attrs[2] = new SimpleAttributeSet(attrs[0]); StyleConstants.setItalic(attrs[2], true); attrs[3] = new SimpleAttributeSet(attrs[0]); StyleConstants.setFontSize(attrs[3], 20); attrs[4] = new SimpleAttributeSet(attrs[0]); StyleConstants.setFontSize(attrs[4], 12); attrs[5] = new SimpleAttributeSet(attrs[0]); StyleConstants.setForeground(attrs[5], Color.red); return attrs; } private void createActionTable(JTextComponent textComponent) { actions = new Hashtable(); Action[] actionsArray = textComponent.getActions(); for (int i = 0; i < actionsArray.length; i++) { Action a = actionsArray[i]; actions.put(a.getValue(Action.NAME), a); } } private Action getActionByName(String name) { return (Action) (actions.get(name)); } class UndoAction extends AbstractAction { public UndoAction() { super("Undo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { 9.7. TEXT KOMPONENTEN 161 try { undo.undo(); } catch (CannotUndoException ex) { System.out.println("Unable to undo: " + ex); ex.printStackTrace(); } updateUndoState(); redoAction.updateRedoState(); } protected void updateUndoState() { if (undo.canUndo()) { setEnabled(true); putValue(Action.NAME, undo.getUndoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Undo"); } } } class RedoAction extends AbstractAction { public RedoAction() { super("Redo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { System.out.println("Unable to redo: " + ex); ex.printStackTrace(); } updateRedoState(); undoAction.updateUndoState(); } protected void updateRedoState() { if (undo.canRedo()) { setEnabled(true); putValue(Action.NAME, undo.getRedoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Redo"); } } } 162 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) public static void main(String[] args) { TextComponentDemo window = new TextComponentDemo(); window.setTitle("TextComponentDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Zum Edieren von Dokumenten werden passende Actions von sogenannten EditorKits bereitgestellt. • Die Aktionen können benutzt werden, um entsprechende Menüs zu erzeugen. • Die Aktionen können durch die Definition von Keymaps an spezielle Eingabezeichen gebunden werden. • Zur Realisierung von undo/redo Funktionen stellt Swing einen UndoManager bereit, der die notwendigen Zustandsinformationen verwaltet. • Die Implementation von undo/redo Aktionen kapselt diese neuen Aktionen. • Mit Hilfe des UndoableEditListener wird der UndoManager mit Informationen versorgt und es werden die undo/redo Aktionen auf den aktuellen Zustand gebracht. 163 9.8. DATEIDIALOGE 9.8 Dateidialoge Die Klasse JFileChooser realisiert Dialoge, mit denen ein Programm einen Benutzer nach Dateinamen fragen kann. Abbildung 9.31: FileChooserDemo // filechooser/FileChooserDemo.java import import import import import import import import import import import import import import import java.io.File; java.awt.Insets; java.awt.BorderLayout; java.awt.event.KeyEvent; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JFrame; javax.swing.JPanel; javax.swing.JMenu; javax.swing.JMenuBar; javax.swing.JMenuItem; javax.swing.JTextArea; javax.swing.JScrollPane; javax.swing.JFileChooser; javax.swing.filechooser.*; public class FileChooserDemo extends JFrame { private JFileChooser fc; private JTextArea log; private JMenuItem saveMenuItem; public FileChooserDemo() { JMenuItem openMenuItem, exitMenuItem; log = new JTextArea(5,20); log.setMargin(new Insets(5,5,5,5)); log.setEditable(false); JScrollPane logScrollPane = new JScrollPane(log); 164 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) fc = new JFileChooser(); fc.addChoosableFileFilter(new TextFilter()); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); JMenu menu = new JMenu("File"); menu.setMnemonic(KeyEvent.VK_F); menuBar.add(menu); openMenuItem = new JMenuItem("Open..."); openMenuItem.setMnemonic(KeyEvent.VK_O); openMenuItem.addActionListener(new OpenListener()); menu.add(openMenuItem); saveMenuItem = new JMenuItem("Save As..."); saveMenuItem.setMnemonic(KeyEvent.VK_C); saveMenuItem.setEnabled(false); saveMenuItem.addActionListener(new SaveListener()); menu.add(saveMenuItem); menu.addSeparator(); exitMenuItem = new JMenuItem("Exit"); exitMenuItem.setMnemonic(KeyEvent.VK_E); exitMenuItem.addActionListener(new ExitListener()); menu.add(exitMenuItem); getContentPane().add(logScrollPane, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } class TextFilter extends FileFilter { public boolean accept(File f) { if (f.isDirectory()) { return true; } String extension = getExtension(f); if (extension != null) { return (extension.equals("txt")); } return false; } public String getExtension(File f) { String ext = null; String s = f.getName(); int i = s.lastIndexOf(’.’); if (i > 0 && i < s.length() - 1) { ext = s.substring(i+1).toLowerCase(); 9.8. DATEIDIALOGE } return ext; } public String getDescription() { return "Text Files (*.txt)"; } } class OpenListener implements ActionListener { public void actionPerformed(ActionEvent event) { int status = fc.showOpenDialog(FileChooserDemo.this); if (status == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); log.append("Opening: " + file.getName() + ".\n"); saveMenuItem.setEnabled(true); } else { log.append("Open command cancelled by user.\n"); } } } class SaveListener implements ActionListener { public void actionPerformed(ActionEvent event) { int status = fc.showSaveDialog(FileChooserDemo.this); if (status == JFileChooser.APPROVE_OPTION) { File file = fc.getSelectedFile(); log.append("Saving: " + file.getName() + ".\n"); } else { log.append("Save command cancelled by user.\n"); } } } class ExitListener implements ActionListener { public void actionPerformed(ActionEvent event) { System.exit(0); } } public static void main(String[] args) { JFrame window = new FileChooserDemo(); window.setTitle("FileChooserDemo"); window.pack(); window.setVisible(true); } 165 166 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) } Bemerkungen: • Die Klasse TextFilter implementiert einen eigenen Filter, mit dem die Menge der Dateien eingeschränkt wird. • Der Dialog zur Dateiauswahl ist modal. • Es gibt verschiedene Möglichkeiten, die Erscheinung des Dialogs (z.B. Beschriftung der Schaltflächen und verwendete Icons) anzupassen. 167 9.9. BÄUME 9.9 Bäume Die Klasse JTree stellt die Funktionalität bereit, Baumstrukturen darzustellen. Ein Modell für die Klasse JTree muss das Interface TreeModel implementieren. Eine Standardimplementation wird durch die Klasse DefaultTreeModel zur Verfügung gestellt. Abbildung 9.32: TreeDemo // tree/TreeDemo.java import import import import import import import import import import import java.awt.Dimension; java.awt.BorderLayout; javax.swing.JTree; javax.swing.JFrame; javax.swing.JTextArea; javax.swing.JScrollPane; javax.swing.JSplitPane; javax.swing.tree.DefaultMutableTreeNode; javax.swing.tree.TreeSelectionModel; javax.swing.event.TreeSelectionEvent; javax.swing.event.TreeSelectionListener; public class TreeDemo extends JFrame { private JTree tree; private JTextArea text; public TreeDemo() { DefaultMutableTreeNode treeModel = createTreeModel(); tree = new JTree(treeModel); tree.getSelectionModel().setSelectionMode (TreeSelectionModel.SINGLE_TREE_SELECTION); tree.addTreeSelectionListener(new TreeListener()); tree.putClientProperty("JTree.lineStyle", "Angled"); final JScrollPane treeView = new JScrollPane(tree); text = new JTextArea(); text.setEditable(false); 168 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) JScrollPane textView = new JScrollPane(text); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setTopComponent(treeView); splitPane.setBottomComponent(textView); Dimension minimumSize = new Dimension(100, 50); textView.setMinimumSize(minimumSize); treeView.setMinimumSize(minimumSize); splitPane.setDividerLocation(200); splitPane.setPreferredSize(new Dimension(500, 300)); getContentPane().add(splitPane, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } private class TreeListener implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if (node == null) return; Entity entity = (Entity) node.getUserObject(); text.setText("Index:\t" + entity.index + "\n" + "Type:\t" + entity.type + "\n" + "Descr:\t" + entity.descr + "\n"); } } public class Entity { private String descr; private String type; private int index; public Entity(int entIndex, String entType, String entDescr) { descr = entDescr; type = entType; index = entIndex; } public String toString() { return descr; } } private DefaultMutableTreeNode createTreeModel() { DefaultMutableTreeNode top = null, lev1 = null, lev2 = null, lev2_1 = null, lev2_1_1 = null, 169 9.9. BÄUME lev3 = null, lev3_1 = null, lev3_1_1 = null, lev3_1_2 = null; top = new DefaultMutableTreeNode(new Entity (1, "chassis", "7206VXR chassis Hw Serial#: 12345")); lev1 = new DefaultMutableTreeNode(new Entity (2, "module", "NPE 3000 Card, Hw Serial#: 12345, Hw Revision: D")); top.add(lev1); lev2 = new DefaultMutableTreeNode(new Entity (3, "container", "Chassis Slot")); top.add(lev2); lev2_1 = new DefaultMutableTreeNode(new Entity (4, "module", "I/O FastEthernet (TX-ISL)")); lev2.add(lev2_1); lev2_1_1 = new DefaultMutableTreeNode(new Entity (5, "port", "DEC21140A")); lev2_1.add(lev2_1_1); lev3 = new DefaultMutableTreeNode(new Entity (6, "container", "Chassis Slot")); top.add(lev3); lev3_1 = new DefaultMutableTreeNode(new Entity (7, "module", "2 Port Fast Ethernet/ISL 100BaseTX Port Adapter")); lev3.add(lev3_1); lev3_1_1 = new DefaultMutableTreeNode(new Entity (8, "port", "AmdFE")); lev3_1.add(lev3_1_1); lev3_1_2 = new DefaultMutableTreeNode(new Entity (9, "port", "AmdFE")); lev3_1.add(lev3_1_2); return top; } public static void main(String[] args) { JFrame window = new TreeDemo(); window.setTitle("TreeDemo"); window.pack(); window.setVisible(true); } } 170 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) 9.10 Tabellen Die Klasse JTable realisiert eine Komponente, mit der Tabellen dargestellt und modifiziert werden können. Das Interface TableModel definiert wiederum eine Schnittstelle für das Modell, das von einer Instanz der Klasse JTable dargestellt wird. Eine abstrakte Implementation des TableModel Interface stellt die Klasse AbstractTableModel bereit. Abbildung 9.33: SimpleTableDemo // simpletable/SimpleTableDemo.java import import import import import import java.awt.Dimension; java.awt.BorderLayout; javax.swing.JTable; javax.swing.JScrollPane; javax.swing.JPanel; javax.swing.JFrame; public class SimpleTableDemo extends JFrame { public SimpleTableDemo() { Object[][] data = { {"Mary", "Campione", "Snowboarding", new Integer(5), new Boolean(false)}, {"Alison", "Huml", "Rowing", new Integer(3), new Boolean(true)}, {"Kathy", "Walrath", "Chasing toddlers", new Integer(2), new Boolean(false)}, {"Mark", "Andrews", "Speed reading", new Integer(20), new Boolean(true)}, {"Angela", "Lih", "Teaching high school", new Integer(4), new Boolean(false)} }; String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"}; JTable table = new JTable(data, columnNames); table.setPreferredScrollableViewportSize(new Dimension(500, 70)); JScrollPane scrollPane = new JScrollPane(table); getContentPane().add(scrollPane, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } 171 9.10. TABELLEN public static void main(String[] args) { SimpleTableDemo window = new SimpleTableDemo(); window.setTitle("SimpleTableDemo"); window.pack(); window.setVisible(true); } } Bemerkungen: • Spalten lassen sich vertauschen und die Breiten der Spalten lassen sich interaktiv anpassen. • Alle Zellen besitzen zunächst dieselbe Breite. • Die Texte in den Zellen werden geeignet abgekürzt. • Die Zellen sind alle einzeln edierbar. • Offensichtlich spielt der Datentyp keine Rolle, da einfach die textuelle Darstellung (toString) benutzt wird. • Entsprechend erscheinen die Zahlen linksbündig statt rechtsbündig. Mehr Kontrolle über die Darstellung bekommt man, indem man selbst eine eigene Klasse von der Klasse AbstractTableModel ableitet und dann als Modell benutzt. Abbildung 9.34: TableDemo // table/TableDemo.java import import import import import import import import import java.awt.Dimension; java.awt.Component; java.awt.BorderLayout; javax.swing.JTable; javax.swing.JScrollPane; javax.swing.JFrame; javax.swing.table.AbstractTableModel; javax.swing.table.TableColumn; javax.swing.table.TableCellRenderer; public class TableDemo extends JFrame { public TableDemo() { SportsTableModel model = new SportsTableModel(); JTable table = new JTable(model); initColumnSizes(table, model); table.setPreferredScrollableViewportSize(new Dimension(500, 70)); 172 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) JScrollPane scrollPane = new JScrollPane(table); getContentPane().add(scrollPane, BorderLayout.CENTER); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } class SportsTableModel extends AbstractTableModel { final String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"}; final Object[][] data = { {"Mary", "Campione", "Snowboarding", new Integer(5), new Boolean(false)}, {"Alison", "Huml", "Rowing", new Integer(3), new Boolean(true)}, {"Kathy", "Walrath", "Chasing toddlers", new Integer(2), new Boolean(false)}, {"Mark", "Andrews", "Speed reading", new Integer(20), new Boolean(true)}, {"Angela", "Lih", "Teaching high school", new Integer(4), new Boolean(false)} }; public int getColumnCount() { return columnNames.length; } public int getRowCount() { return data.length; } public String getColumnName(int col) { return columnNames[col]; } public Object getValueAt(int row, int col) { return data[row][col]; } public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } public boolean isCellEditable(int row, int col) { return (col > 1); } 9.10. TABELLEN 173 public Object getLongestColumnValue(int col) { Object longest = null; for (int i = 0; i < getRowCount(); i++) { Object val = getValueAt(i, col); if ((longest == null) || (val.toString().length() > longest.toString().length())) { longest = val; } } return longest; } public void setValueAt(Object value, int row, int col) { data[row][col] = value; fireTableCellUpdated(row, col); } } private void initColumnSizes(JTable table, SportsTableModel model) { Component comp = null; int headerWidth = 0; int cellWidth = 0; TableCellRenderer renderer; for (int i = 0; i < model.getColumnCount(); i++) { TableColumn column = table.getColumnModel().getColumn(i); renderer = table.getTableHeader().getDefaultRenderer(); comp = renderer.getTableCellRendererComponent( null, column.getHeaderValue(), false, false, 0, 0); headerWidth = comp.getPreferredSize().width; renderer = table.getDefaultRenderer(model.getColumnClass(i)); comp = renderer.getTableCellRendererComponent( table, model.getLongestColumnValue(i), false, false, 0, i); cellWidth = comp.getPreferredSize().width; column.setPreferredWidth(Math.max(headerWidth, cellWidth)); } } public static void main(String[] args) { TableDemo window = new TableDemo(); window.setTitle("TableDemo"); window.pack(); window.setVisible(true); } 174 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) } Bemerkungen: • Die Methode getColumnClass wird offensichtlich benutzt, um abhängig vom Datentyp der Spalte eine passende Darstellung zu wählen. • Eine Vertauschung der Spalten durch den Benutzer wirkt sich nicht auf die Nummerierung der Spalten im Modell aus. • Die fireTableCellUpdated Methode wird benutzt, um nach Änderungen passende Ereignisse zu erzeugen. • Die Ereignisse können mit Hilfe eines TableModelListener eingefangen werden. • Spezielle Hilfsklassen sind für die Darstellung der Zellen verantwortlich. Die allgemeine Schnittstelle ist im Interface TableCellRenderer festgelegt. • Andere Hilfsklassen sind für das Edieren der Zellen verantwortlich. Die allgemeine Schnittstelle ist im Interface TableCellEditor definiert. 9.11 Zweidimensionale Graphik Die Java 2D Programmierschnittstelle erlaubt die Darstellung von zwei-dimensionalen Graphiken, Texten und Bildern. Die Schnittstelle ist eine Erweiterung der Graphikklassen des AWT. Die Java 2D Schnittstelle trennt die Beschreibung von graphischen Objekten wie z.B. Linien von der eigentlichen Darstellung (rendering). Dadurch ist möglich, Graphiken durch die Verwendung verschiedener Darstellungsalgorithmen (renderer) auf unterschiedlichen Geräten und Druckern zu reproduzieren. Object Graphics Graphics2D Abbildung 9.35: Java 2D Graphik-Klassen Java 2D unterscheidet zwei Koordinatensysteme: • User Space: Das Koordinatensystem in dem ein Java Programm graphische Objekte erzeugt und verwaltet. Dieses Koordinatensystem ist geräteunabhängig und hat seinen Ursprung (der Punkt (0,0)) in der oberen linken Ecke. 9.11. ZWEIDIMENSIONALE GRAPHIK 175 • Device Space: Das Koordinatensystem eines konkreten Ausgabegeräts (z.B. Drucker, Bildschirm). Die Abbildung vom geräteunabhängigen Koordinatensystem auf das geräteabhängige Koordinatensystem erfolgt automatisch beim rendering. Bei der Transformation auf ein Ausgabegerät kann die Darstellungsweise durch einen sogenannten rendering context beeinflusst werden: • pen style: Wird auf den Umriss eines graphischen Objekts angewendet und erlaubt die Darstellung von Linien in unterschiedlicher Breite, mit verschiedenen Strichmustern und mit unterschiedlichen Darstellungen der Enden. • fill style: Wird auf die Fläche eines graphischen Objekts angewendet. Flächen können mit einfachen Farben, mit verlaufenden Farben oder Mustern gefüllt werden. • composition style: Bestimmt die Darstellung, wenn sich Objekte überlappen. • transform: Bestimmt die Abbildung der Koordinaten auf das Koordinatensystem des Darstellungsgeräts und ermöglicht die Translation, Rotation, Skalierung. • clip: Beschränkt die Fläche, die neu dargestellt wird. • font: Texte werden mit Hilfe des Fonts zu darstellbaren Glyphs“ konvertiert. ” • rendering hints: Weitere Parameter, die insbesondere den trade-off zwischen Geschwindigkeit und Qualität beeinflussen. Der rendering context einer Instanz der Klasse Graphics2D kann durch entsprechende Methoden (setStroke, setPaint, setComposite, setTransform, setClip, setFont, setRenderingHints) manipuliert werden. Swing Komponenten stellen sich generell selbst dar. Dazu dienen die Methoden repaint() und update(), die in der Regel automatisch aufgerufen wird, wenn ein graphisches Objekt dargestellt werden muss oder wenn sich der Zustand eines graphischen Objekts verändert hat. Ein repaint() ist rekursiv und führt automatisch dazu, dass alle enthaltenen Objekte ebenfalls ein repaint() ausführen. Die eigentliche graphische Darstellung eines graphischen Objekts erfolgt in der öffentlichen paint() Methode, die ihrerseits wiederum die geschützten Methoden paintComponent(), paintBorder() und paintChildren() in dieser Reihenfolge aufruft. Ein Aufruf von update() bewirkt ebenfalls einen Aufruf von paint(). Abbildung 9.36: SwingPaintDemo // swingpaint/SwingPaintDemo.java import import import import import java.awt.Color; java.awt.Graphics; java.awt.Dimension; javax.swing.JFrame; javax.swing.JPanel; class BullsEyePanel extends JPanel 176 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) { public Dimension getPreferredSize() { // Figure out what the layout manager needs, take the // maximum of (width, height, 100) and request a // quadratic size in order to produce a ’round’ bullseye. Dimension layoutSize = super.getPreferredSize(); int max = Math.max(Math.max(layoutSize.width, layoutSize.height), 100); return new Dimension(max, max); } protected void paintComponent(Graphics g) { Dimension size = getSize(); int x = 0; int y = 0; int i = 0; g.clearRect(0, 0, size.width, size.height); while (x < size.width && y < size.height) { g.setColor((i%2 == 0) ? Color.red : Color.white); g.fillOval(x, y, size.width-(2*x), size.height-(2*y)); x += 10; y += 10; i++; } } } class SwingPaintDemo { public static void main(String[] args) { JFrame f = new JFrame("SwingPaintDemo"); f.setContentPane(new BullsEyePanel()); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.pack(); f.show(); } } 9.11.1 Primitive geometrische Formen (Shapes) Die graphischen Primitive sind in dem Paket java.awt.geom definiert. Das Interface Shape definiert eine allgemeine geometrische Form. 177 9.11. ZWEIDIMENSIONALE GRAPHIK Arc2D.Double Arc2D Arc2D.Float «interface» Shape Ellipse2D.Double Ellipse2D Ellipse2D.Float Rectangle RectangularShape Rectangle2D Rectangle2D.Double Rectangle2D.Float Object Area RoundRectangle2D.Double RoundRectangle2D RoundRectangle2D.Float Polygon GeneralPath Line2D.Double Line2D Line2D.Float QuadCurve2D.Double QuadCurve2D QuadCurve2D.Float CubicCurve2D.Double CubicCurve2D CubicCurve2D.Float Abbildung 9.37: Java 2D Shapes Das folgende Programm demonstriert wie diese Shapes dargestellt werden können und wie die Darstellungsweise beeinflusst werden kann. Abbildung 9.38: ShapesDemo // shapes/ShapesDemo.java import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; 178 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) import import import import import import import import import import import import import import import java.awt.Graphics2D; java.awt.BasicStroke; java.awt.Font; java.awt.FontMetrics; java.awt.RenderingHints; java.awt.GradientPaint; java.awt.Container; java.awt.geom.Arc2D; java.awt.geom.Line2D; java.awt.geom.Rectangle2D; java.awt.geom.RoundRectangle2D; java.awt.geom.Ellipse2D; java.awt.geom.GeneralPath; javax.swing.JFrame; javax.swing.JPanel; public class ShapesDemo extends JPanel { final static int maxCharHeight = 15; final static int minFontSize = 6; final final final final static static static static Color Color Color Color bg = Color.white; fg = Color.black; red = Color.red; white = Color.white; final static BasicStroke stroke = new BasicStroke(2.0f); final static BasicStroke wideStroke = new BasicStroke(8.0f); final static float dash1[] = {10.0f}; final static BasicStroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); Dimension totalSize; FontMetrics fontMetrics; public ShapesDemo() { super(); setBackground(bg); setForeground(fg); } public FontMetrics pickFont(Graphics2D g2, String longString, int xSpace) { boolean fontFits = false; Font font = g2.getFont(); FontMetrics fontMetrics = g2.getFontMetrics(); int size = font.getSize(); String name = font.getName(); int style = font.getStyle(); 9.11. ZWEIDIMENSIONALE GRAPHIK 179 while (! fontFits) { if ( (fontMetrics.getHeight() <= maxCharHeight) && (fontMetrics.stringWidth(longString) <= xSpace) ) { fontFits = true; } else { if ( size <= minFontSize ) { fontFits = true; } else { g2.setFont(font = new Font(name, style, --size)); fontMetrics = g2.getFontMetrics(); } } } return fontMetrics; } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Dimension d = getSize(); int gridWidth = d.width / 6; int gridHeight = d.height / 2; g2.clearRect(0, 0, d.width, d.height); fontMetrics = pickFont(g2, "Filled and Stroked GeneralPath", gridWidth); Color fg3D = Color.lightGray; g2.setPaint(fg3D); g2.draw3DRect(0, 0, d.width - 1, d.height - 1, true); g2.draw3DRect(3, 3, d.width - 7, d.height - 7, false); g2.setPaint(fg); int int int int int x = 5; y = 7; rectWidth = gridWidth - 2*x; stringY = gridHeight - 3 - fontMetrics.getDescent(); rectHeight = stringY - fontMetrics.getMaxAscent() - y - 2; // draw Line2D.Double g2.draw(new Line2D.Double(x, y+rectHeight-1, x + rectWidth, y)); g2.drawString("Line2D", x, stringY); x += gridWidth; // draw Rectangle2D.Double g2.setStroke(stroke); g2.draw(new Rectangle2D.Double(x, y, rectWidth, rectHeight)); g2.drawString("Rectangle2D", x, stringY); x += gridWidth; 180 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) // draw RoundRectangle2D.Double g2.setStroke(dashed); g2.draw(new RoundRectangle2D.Double(x, y, rectWidth, rectHeight, 10, 10)); g2.drawString("RoundRectangle2D", x, stringY); x += gridWidth; // draw Arc2D.Double g2.setStroke(wideStroke); g2.draw(new Arc2D.Double(x, y, rectWidth, rectHeight, 90, 135, Arc2D.OPEN)); g2.drawString("Arc2D", x, stringY); x += gridWidth; // draw Ellipse2D.Double g2.setStroke(stroke); g2.draw(new Ellipse2D.Double(x, y, rectWidth, rectHeight)); g2.drawString("Ellipse2D", x, stringY); x += gridWidth; // draw GeneralPath (polygon) int x1Points[] = {x, x+rectWidth, x, x+rectWidth}; int y1Points[] = {y, y+rectHeight, y+rectHeight, y}; GeneralPath polygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, x1Points.length); polygon.moveTo(x1Points[0], y1Points[0]); for (int index = 1; index < x1Points.length; index++) { polygon.lineTo(x1Points[index], y1Points[index]); }; polygon.closePath(); g2.draw(polygon); g2.drawString("GeneralPath (polygon)", x, stringY); // NEW ROW x = 5; y += gridHeight; stringY += gridHeight; // draw GeneralPath (polyline) int x2Points[] = {x, x+rectWidth, x, x+rectWidth}; int y2Points[] = {y, y+rectHeight, y+rectHeight, y}; GeneralPath polyline = new GeneralPath(GeneralPath.WIND_EVEN_ODD, x2Points.length); polyline.moveTo (x2Points[0], y2Points[0]); for (int index = 1; index < x2Points.length; index++) { polyline.lineTo(x2Points[index], y2Points[index]); }; g2.draw(polyline); g2.drawString("GeneralPath (polyline)", x, stringY); x += gridWidth; 9.11. ZWEIDIMENSIONALE GRAPHIK 181 // fill Rectangle2D.Double (red) g2.setPaint(red); g2.fill(new Rectangle2D.Double(x, y, rectWidth, rectHeight)); g2.setPaint(fg); g2.drawString("Filled Rectangle2D", x, stringY); x += gridWidth; // fill RoundRectangle2D.Double GradientPaint redtowhite; redtowhite = new GradientPaint(x, y, red, x+rectWidth, y, white); g2.setPaint(redtowhite); g2.fill(new RoundRectangle2D.Double(x, y, rectWidth, rectHeight, 10, 10)); g2.setPaint(fg); g2.drawString("Filled RoundRectangle2D", x, stringY); x += gridWidth; // fill Arc2D g2.setPaint(red); g2.fill(new Arc2D.Double(x, y, rectWidth, rectHeight, 90, 135, Arc2D.OPEN)); g2.setPaint(fg); g2.drawString("Filled Arc2D", x, stringY); x += gridWidth; // fill Ellipse2D.Double redtowhite = new GradientPaint(x, y, red, x+rectWidth, y, white); g2.setPaint(redtowhite); g2.fill (new Ellipse2D.Double(x, y, rectWidth, rectHeight)); g2.setPaint(fg); g2.drawString("Filled Ellipse2D", x, stringY); x += gridWidth; // fill and stroke GeneralPath int x3Points[] = {x, x+rectWidth, x, x+rectWidth}; int y3Points[] = {y, y+rectHeight, y+rectHeight, y}; GeneralPath filledPolygon = new GeneralPath(GeneralPath.WIND_EVEN_ODD, x3Points.length); filledPolygon.moveTo(x3Points[0], y3Points[0]); for (int index = 1; index < x3Points.length; index++) { filledPolygon.lineTo(x3Points[index], y3Points[index]); }; filledPolygon.closePath(); g2.setPaint(red); g2.fill(filledPolygon); g2.setPaint(fg); g2.draw(filledPolygon); g2.drawString("Filled and Stroked GeneralPath", x, stringY); } public static void main(String s[]) { JFrame f = new JFrame("ShapesDemo"); 182 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) «interface» Runnable JPanel 0..* +Ball(color:Color,name:String,bsize:int) +getName(): String +getRadius(): int +setRadius(radius:int): void -makeImages(bsize:int): void -blend(fg:int,bg:int,fgfactor:float): int +step(deltaT:long,w:int,h:int): void bounces BallsThread Ball 1 +BallsThread() +step(): void +paint(g:Graphics): void +start(): void +stop(): void 1 +run(): void 1 BallsDemo contains +BallsDemo() +start(): void +stop(): void +exit(): void #makeFileMenu(menuBar:JMenuBar): void #makeBallsMenu(menuBar:JMenuBar): void #makeSizeMenu(menuBar:JMenuBar): void Abbildung 9.40: UML Diagramm für die Animation springender Bälle f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.getContentPane().add(new ShapesDemo()); f.pack(); f.setSize(new Dimension(550,100)); f.setVisible(true); } } 9.11.2 Beispiel: Animation springender Bälle Die folgende Applikation zeigt ein paar springende Bälle, wobei sich die Farbverläufe auf den Bällen stetig leicht ändert. Das Beispiel demonstriert, wie man selbst in einem Puffer eine beliebige Graphik erstellen kann. Abbildung 9.39: BallsDemo Die Implementation besteht aus drei wesentlichen Klassen: 1. Die Klasse BallsDemo hat die Aufgabe, das Fenster und die Menüs aufzubauen und zu verwalten. 9.11. ZWEIDIMENSIONALE GRAPHIK 183 2. Die Klasse BallsThread realisiert einen Thread, der periodisch die Darstellung der Bälle anstößt und in der paint-Methode darstellt. 3. Die Klasse Ball repräsentiert einen Ball. Zu jedem Ball existieren mehrere Darstellungen in leicht verschiedenen Farbverläufen. // balls/BallsDemo.java import import import import import import import import import import import import import import import import import import import import import import import java.awt.Color; java.awt.Dimension; java.awt.Graphics; java.awt.Graphics2D; java.awt.image.IndexColorModel; java.awt.image.DataBufferByte; java.awt.image.Raster; java.awt.image.WritableRaster; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.event.ItemEvent; java.awt.event.ItemListener; java.awt.event.WindowEvent; java.awt.event.WindowAdapter; java.awt.image.BufferedImage; javax.swing.JMenuBar; javax.swing.JMenu; javax.swing.JMenuItem; javax.swing.JCheckBoxMenuItem; javax.swing.JRadioButtonMenuItem; javax.swing.ButtonGroup; javax.swing.JPanel; javax.swing.JFrame; /** * The BallsDemo class demonstrates animated colored bouncing balls. */ public class BallsDemo extends JFrame { private BallsThread bThread; private JMenuItem stopItem; private JMenuItem stepItem; private JMenuItem startItem; public BallsDemo() { bThread = new BallsThread(); getContentPane().add(bThread); JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); makeFileMenu(menuBar); makeBallsMenu(menuBar); makeSizeMenu(menuBar); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { exit(); } 184 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) public void windowDeiconified(WindowEvent e) { start(); } public void windowIconified(WindowEvent e) { stop(); } }); } public void start() { bThread.start(); startItem.setEnabled(false); stopItem.setEnabled(true); stepItem.setEnabled(false); } public void stop() { bThread.stop(); startItem.setEnabled(true); stopItem.setEnabled(false); stepItem.setEnabled(true); } public void exit() { System.exit(0); } private void makeFileMenu(JMenuBar menuBar) { JMenu menu = new JMenu("File"); menuBar.add(menu); startItem = new JMenuItem("Start"); startItem.addActionListener(new StartStopListener(startItem)); menu.add(startItem); stopItem = new JMenuItem("Stop"); stopItem.addActionListener(new StartStopListener(stopItem)); menu.add(stopItem); stepItem = new JMenuItem("Step"); stepItem.addActionListener(new StartStopListener(stepItem)); menu.add(stepItem); menu.addSeparator(); JMenuItem item = new JMenuItem("Exit"); item.addActionListener(new ExitListener()); menu.add(item); } private class StartStopListener implements ActionListener { JMenuItem item; public StartStopListener(JMenuItem item) { 9.11. ZWEIDIMENSIONALE GRAPHIK 185 this.item = item; } public void actionPerformed(ActionEvent e) { if (e.getSource() == startItem) start(); if (e.getSource() == stopItem) stop(); if (e.getSource() == stepItem) { bThread.step(bThread.getSize()); bThread.repaint(); } } } private class ExitListener implements ActionListener { public void actionPerformed(ActionEvent e) { exit(); } } private void makeBallsMenu(JMenuBar menuBar) { JCheckBoxMenuItem item; JMenu menu = new JMenu("Balls"); menuBar.add(menu); for (int i = 0; i < bThread.balls.length; i++) { item = new JCheckBoxMenuItem(bThread.balls[i].getName()); item.addItemListener(new BallsListener(bThread.balls[i])); item.setState(true); menu.add(item); } } private class BallsListener implements ItemListener { private Ball ball; public BallsListener(Ball ball) { this.ball = ball; } public void itemStateChanged(ItemEvent e) { ball.isSelected = (e.getStateChange() == ItemEvent.SELECTED); } } private void makeSizeMenu(JMenuBar menuBar) { JRadioButtonMenuItem item; 186 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) JMenu menu = new JMenu("Radius"); menuBar.add(menu); ButtonGroup group = new ButtonGroup(); for (int i = 10; i < 90; i += 10) { item = new JRadioButtonMenuItem(i + " pt"); item.addActionListener(new SizeListener(i)); if (i == bThread.balls[0].getRadius()) item.setSelected(true); group.add(item); menu.add(item); } } private class SizeListener implements ActionListener { private int size; public SizeListener(int size) { this.size = size; } public void actionPerformed(ActionEvent e) { JRadioButtonMenuItem item = (JRadioButtonMenuItem) e.getSource(); for (int i = 0; i < bThread.balls.length; i++) { bThread.balls[i].setRadius(size); } } } /** * The BallsThread class performs the animation and painting. */ static class BallsThread extends JPanel implements Runnable { private static Color colors[] = { Color.red, Color.orange, Color.yellow, Color.green, Color.blue, Color.magenta, Color.gray }; private static String names[] = { "red", "orange", "yellow", "green", "blue", "magenta", "gray" }; protected Ball balls[] = new Ball[colors.length]; private Thread thread; private BufferedImage bimg; private long now, lasttime; /** * Creates a new ball object for each color in the colors array. */ public BallsThread() { setBackground(Color.white); for (int i = 0; i < colors.length; i++) { balls[i] = new Ball(colors[i], names[i], 40); 9.11. ZWEIDIMENSIONALE GRAPHIK 187 } } /** * Move the balls one step further and kick them randomly if * they are not active enough anymore. */ public void step(Dimension d) { if (lasttime == 0) { lasttime = System.currentTimeMillis(); } now = System.currentTimeMillis(); long delta = now - lasttime; boolean active = false; for (int i = 0; i < balls.length; i++) { balls[i].step(delta, d.width, d.height); if (Math.abs(balls[i].Vy) > .02 || (balls[i].y + 2 * balls[i].getRadius() < d.height)) { active = true; } } if (! active) { for (int i = 0; i < balls.length; i++) { balls[i].Vx = (float) Math.random() / 4.0f - 0.125f; balls[i].Vy = -(float) Math.random() / 4.0f - 0.2f; } } } /** * Indirectly called via repaint() to update the image. This is * implemented by creating an image buffer with its own graphics * context and drawing all selected balls with their current image. * Finally, the image buffer is displayed on the screen. */ public void paint(Graphics g) { Dimension d = getSize(); BufferedImage bimg = (BufferedImage) createImage(d.width, d.height); Graphics2D g2 = bimg.createGraphics(); g2.setBackground(getBackground()); g2.clearRect(0, 0, d.width, d.height); for (int i = 0; i < balls.length; i++) { Ball b = balls[i]; if (b.imgs[b.index] != null && b.isSelected) { g2.drawImage(b.imgs[b.index], (int) b.x, (int) b.y, this); } } 188 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) lasttime = now; g2.dispose(); g.drawImage(bimg, 0, 0, this); } /** * Create a new thread to draw the bouncing balls. */ public void start() { thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); } /** * Stop the thread by setting the variable which references * the thread to null. */ public synchronized void stop() { thread = null; } /** * Repaint the window every 10 milliseconds as long as the * variable references this thread. */ public void run() { Thread me = Thread.currentThread(); while (thread == me) { step(getSize()); repaint(); try { thread.sleep(10); } catch (InterruptedException e) { break; } } thread = null; } } /** * The Ball class creates 5 ball images. Each ball image * increasingly blends the color white with the specified color. */ static class Ball { public float x, y; public float Vx = 0.1f; public float Vy = 0.05f; final private int nImgs = 5; public BufferedImage imgs[]; 9.11. ZWEIDIMENSIONALE GRAPHIK 189 public int index = (int) (Math.random() * (nImgs-1)); static static static static static private private private private private private private private private private final final final final final float inelasticity = .96f; float Ax = 0.0f; float Ay = 0.0002f; int UP = 0; int DOWN = 1; int indexDirection = UP; final Color color; final String name; int radius; boolean isSelected; public Ball(Color color, String name, int radius) { this.color = color; this.name = name; setRadius(radius); } public String getName() { return name; } public int getRadius() { return radius; } public void setRadius(int radius) { makeImages(radius); } /** * Manually compute the circle and the colored images * for the ball. */ private void makeImages(int bsize) { radius = bsize; int R = bsize; byte[] data = new byte[R * 2 * R * 2]; int maxr = 0; for (int Y = 2 * R; --Y >= 0;) { int x0 = (int) (Math.sqrt(R * R - (Y - R) * (Y - R)) + 0.5); int p = Y * (R * 2) + R - x0; for (int X = -x0; X < x0; X++) { int x = X + 15; int y = Y - R + 15; int r = (int) (Math.sqrt(x * x + y * y) + 0.5); if (r > maxr) { 190 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) maxr = r; } data[p++] = r <= 0 ? 1 : (byte) r; } } imgs = new BufferedImage[nImgs]; int bg = 255; byte red[] = new byte[256]; red[0] = (byte) bg; byte green[] = new byte[256]; green[0] = (byte) bg; byte blue[] = new byte[256]; blue[0] = (byte) bg; // for each image, set its color... for (int r = 0; r < imgs.length; r++) { float b = 0.5f + (float) ((r+1f)/imgs.length/2f); for (int i = maxr; i >= 1; --i) { float d = (float) i / maxr; red[i] = (byte) blend(blend(color.getRed(), 255, d), bg, b); green[i] = (byte) blend(blend(color.getGreen(), 255, d), bg, b); blue[i] = (byte) blend(blend(color.getBlue(), 255, d), bg, b); } IndexColorModel icm; DataBufferByte dbb; WritableRaster wr; int bandOffsets[] = {0}; icm = new IndexColorModel(8, maxr + 1, red, green, blue, 0); dbb = new DataBufferByte(data, data.length); wr = Raster.createInterleavedRaster(dbb, R*2, R*2, R*2, 1, bandOffsets,null); imgs[r] = new BufferedImage(icm, wr, icm.isAlphaPremultiplied(), null); } } /** * Compute the blending of foreground and background colors. */ private final int blend(int fg, int bg, float fgfactor) { return (int) (bg + (fg - bg) * fgfactor); } /** * Move the ball and bounce it off the window "walls". * Select the next ball image by updating the image index. */ public void step(long deltaT, int w, int h) 191 9.11. ZWEIDIMENSIONALE GRAPHIK { float jitter = (float) Math.random() * .01f - .005f; int diameter = radius * 2; x += Vx * deltaT + (Ax / 2.0) y += Vy * deltaT + (Ay / 2.0) if (x <= 0.0f) { x = 0.0f; Vx = -Vx * inelasticity + } if (x + diameter >= w) { x = w - diameter; Vx = -Vx * inelasticity + } if (y <= 0) { y = 0; Vy = -Vy * inelasticity + } if (y + diameter >= h) { y = h - diameter; Vx *= inelasticity; Vy = -Vy * inelasticity + } Vy = Vy + Ay * deltaT; Vx = Vx + Ax * deltaT; * deltaT * deltaT; * deltaT * deltaT; jitter; jitter; jitter; jitter; if (indexDirection == UP) { index++; } if (indexDirection == DOWN) { --index; } if (index+1 == nImgs) { indexDirection = DOWN; } if (index == 0) { indexDirection = UP; } } } /** * The usual entry point... */ public static void main(String argv[]) { final BallsDemo window = new BallsDemo(); window.setTitle("BallsDemo"); window.pack(); window.setSize(new Dimension(400,300)); window.setVisible(true); window.start(); } } 192 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) Bemerkungen: • Ein ColorModel hat die Aufgaben, Pixelwerten Farbkomponenten (z.B. rot, grün, blau) und eine Alpha-Komponente zuzuordnen. Die abgeleitete Klasse IndexColorModel unterstützt Pixelwerte, die einen Index in eine feste Farbtabelle darstellen. • Ein Raster repräsentiert ein rechteckiges Feld von Pixeln. Die Daten werden in einem DataBuffer verwaltet. Ein WritableRaster erlaubt das Schreiben von Pixel Eigenschaften im Raster. • Der im Programm verwendete Konstruktor der Klasse BufferedImage nimmt eine Instanz eines ColorModels und ein WritableRaster entgegen und erzeugt daraus ein darstellbares Image. 9.11.3 Beispiel: Santa Claus Zum Abschluss noch ein kleiner Gruß von Santa Claus zur kommenden Weihnachtspause... Abbildung 9.41: SantaClaus // santaclaus/SantaClaus.java import import import import import import import java.awt.Color; java.awt.Dimension; java.awt.Graphics; java.awt.Graphics2D; javax.swing.JFrame; javax.swing.JPanel; javax.swing.ImageIcon; public class SantaClaus { private SantaClausThread scThread; public SantaClaus() { JFrame frame = new JFrame(); scThread = new SantaClausThread(); frame.getContentPane().add(scThread); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setTitle("Santa Claus is coming soon..."); frame.pack(); frame.setSize(new Dimension(400,300)); frame.setVisible(true); 9.11. ZWEIDIMENSIONALE GRAPHIK 193 start(); } public void start() { scThread.start(); } public void stop() { scThread.stop(); } private class SantaClausThread extends JPanel implements Runnable { private final int num = 4; private ImageIcon imgs[]; private Thread thread; private int index = 0; private int x = 42; private int y = 0; private int dx = 0; public SantaClausThread() { setBackground(Color.white); imgs = new ImageIcon[num]; for (int i = 0; i < num; i++) { imgs[i] = new ImageIcon("xmas" + i + ".gif"); } } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; ImageIcon i = imgs[index]; Dimension d = getSize(); Dimension c = new Dimension(i.getIconWidth(), i.getIconHeight()); g2.setBackground(getBackground()); g2.clearRect(0, 0, d.width, d.height); if (y == d.height - c.height) { if (x + c.width < 0 || x > d.width) { x = (int) (Math.random() * (d.width - c.width)); y = 0; dx = 0; } else { i.paintIcon(this, g2, x, y); x += dx; } } else { if (y + c.height < d.height) { i.paintIcon(this, g2, x, y); y += 5; index = (index + 1) % num; 194 KAPITEL 9. JAVA FOUNDATION CLASSES (SWING) } else { y = d.height - c.height; i.paintIcon(this, g2, x, y); dx = ((x + c.width/2) > d.width/2) ? 5 : -5; index = 0; } } } public void start() { thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); } public synchronized void stop() { thread = null; } public void run() { Thread me = Thread.currentThread(); while (thread == me) { repaint(); try { thread.sleep(80); } catch (InterruptedException e) { break; } } thread = null; } } public static void main(String argv[]) { final SantaClaus sc = new SantaClaus(); } } Teil III Software-Komponenten und Verteilte Anwendungen 195 Kapitel 10 Java Beans Mit Software-Komponenten versucht man wiederverwendbare Bausteine zu realisieren, die man anschließend mit speziellen Werkzeugen verknüpfen und zu neuen Anwendungen zusammenstecken kann (LegoPrinzip). Ziel ist es letztlich, die Entwicklungszeit durch die Wiederverwendung und den Einsatz von Werkzeugen drastisch zu verkürzen. Damit eine Software-Komponenten von Werkzeugen als solche erkannt und manipuliert werden kann, müssen Informationen über diese Komponente vorhanden sein: • Informationen über Parameter, die während der Komposition verändert werden können (properties). • Informationen über von der Komponente realisierten Schnittstellen. • Informationen über die Art und Weise, mit der mit einer Komponente kommuniziert werden kann. • Meta-Informationen zur Verwaltung und Darstellung der Komponente in Werkzeugen. Die Java Beans sind eine Realisierung dieser Idee auf der Grundlage von Java. Im wesentlichen unterscheidet sich eine Bean kaum von einer normalen Java Klasse, da durch die Möglichkeit der Introspektion bereits viele notwendige Informationen zugreifbar sind. Es müssen jedoch bei der Programmierung einer Bean einige Regeln eingehalten werden, damit die mit Introspektion nicht ermittelbaren Informationen von Werkzeugen zugreifbar sind. 10.1 Properties Properties sind parametrisierbare Eigenschaften einer Bean, die von einem Programm während der Komposition manipuliert werden können. • Properties sind private-Daten einer Bean, auf die mit public-Methoden zugegriffen wird. • Die Zugriffsmethoden für die Properties einer Bean müssen gewissen Namenskonventionen genügen (get<Property>, set<Property>, is<Property>). • Simple Properties repräsentieren einen einzelnen Wert. • Indexed Properties repräsentieren mehrere Werte in einem Array. • Bound Properties benachrichtigen andere Objekte, die sich als Listener angemeldet haben, wenn sich ihr Wert ändert. • Constrained Properties müssen Objekte, die sich als Listener angemeldet haben, vor einer Änderung um Erlaubnis fragen. 197 198 KAPITEL 10. JAVA BEANS • Die Manipulation der Properties erfolgt durch Property-Editoren. Für Standard-Datentypen gibt es vorgefertigte Property-Editoren. Neue Property-Editoren können durch die Implementierung des Interfaces PropertyEditor implementiert werden. • Bei komplexeren Beans kann der Entwickler auch einen Customizer realisieren, der den Anwender beim Einstellen der Properties unterstützt. Ein Customizer implementiert das Interface Customizer. Das folgende Beispiel ist eine Bean mit einfachen Properties und indizierten Properties, wobei die Properties verschiedenen Datentypen besitzen. // beans/SimpleBean.java public class SimpleBean implements java.io.Serializable { // simple properties except boolean: private int number = 0; public void setNumber(int value) { number = value; } public int getNumber() { return number; } // boolean properties are somewhat special private boolean active = false; public void setActive(boolean value) { active = value; } public boolean isActive() { return active; } // indexed properties: private float[] points; public void setPoints(int index, float value) { points[index] = value; } public void setPoints(float[] values) { points = values; } 199 10.1. PROPERTIES public float getPoints(int index) { return points[index]; } public float[] getPoints() { return points; } } Bei gebundenen Properties werden registrierte Objekte mit einem PropertyChangeEvents über Änderungen benachrichtigt. Objekte, die über Änderungen benachrichtigt werden wollen, müssen entsprechend das Interface PropertyChangeListener implementieren. Bei der Verwaltung der registrierten Listener hilft die Klasse PropertyChangeSupport. «interface» EventListener «interface» PropertyChangeListener +propertyChange(e:PropertyChangeEvent): void PropertyChangeSupport +addPropertyChangeListener(pcl:PropertyChangeListener): void +removePropertyChangeListener(pcl:PropertyChangeListener): void Object EventObject PropertyChangeEvent Abbildung 10.1: Java Klassen und Interfaces für gebundene Properties // beans/BoundBean.java import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeListener; public class BoundBean implements java.io.Serializable { private PropertyChangeSupport pcs; public static final String PRICE = "price"; private int price; public BoundBean() { pcs = new PropertyChangeSupport(this); } public void addPropertyChangeListener(PropertyChangeListener pcl) 200 KAPITEL 10. JAVA BEANS { pcs.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { pcs.removePropertyChangeListener(pcl); } public void setPrice(int newValue) { if (newValue != price) { int oldValue = price; price = newValue; pcs.firePropertyChange(PRICE, new Integer(oldValue), new Integer(newValue)); } } public int getPrice() { return price; } } Bemerkungen: • Die Erzeugung eines PropertyChangeEvents muss explizit programmiert werden. Dabei sind der alte und der neue Wert sowie der Name des Properties zu übergeben. • PropertyChangeEvents werden grundsätzlich erst nach der Änderung einer Property erzeugt. • Es empfiehlt sich keine PropertyChangeEvents zu generieren, wenn sich der Wert einer Property nicht geändert hat, da es durchaus zu Schleifen kommen kann, wenn ein Listener direkt oder indirekt wieder die set<Property>-Methode aufruft. Der typische Ablauf ist in folgendem Sequenzdiagramm dargestellt. 201 10.1. PROPERTIES :Sender setNumber :BoundBean addPropertyChangeListener propertyChange :Listener Abbildung 10.2: Sequenzdiagramm für Bound Properties // beans/BoundBeanListener.java import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; public class BoundBeanListener implements PropertyChangeListener { private BoundBean boundBean; public BoundBeanListener(BoundBean bb) { boundBean = bb; boundBean.addPropertyChangeListener(this); } public void propertyChange(PropertyChangeEvent e) { if (e.getSource() == boundBean && e.getPropertyName() == BoundBean.PRICE) { System.out.println("New price: " + e.getNewValue()); } } } 202 KAPITEL 10. JAVA BEANS Die Implementierung von Constrained Properties erfolgt analog zu Bound Properties. Die Listener müssen hierbei das VetoableChangeListener Interface implementieren und können die Änderung des Datenfelds durch das Werfen einer PropertyVetoException verhindern. // beans/VetoableBean.java import import import import import java.beans.PropertyChangeSupport; java.beans.PropertyChangeListener; java.beans.VetoableChangeSupport; java.beans.VetoableChangeListener; java.beans.PropertyVetoException; public class VetoableBean implements java.io.Serializable { private PropertyChangeSupport pcs; private VetoableChangeSupport vcs; public static final String PRICE = "price"; private int price; public VetoableBean() { pcs = new PropertyChangeSupport(this); vcs = new VetoableChangeSupport(this); } public void addPropertyChangeListener(PropertyChangeListener pcl) { pcs.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { pcs.removePropertyChangeListener(pcl); } public void addVetoableChangeListener(VetoableChangeListener vcl) { vcs.addVetoableChangeListener(vcl); } public void removeVetoableChangeListener(VetoableChangeListener vcl) { vcs.removeVetoableChangeListener(vcl); } public void setPrice(int newValue) throws PropertyVetoException { if (newValue != price) { int oldValue = price; vcs.fireVetoableChange(PRICE, new Integer(oldValue), new Integer(newValue)); price = newValue; pcs.firePropertyChange(PRICE, 203 10.1. PROPERTIES new Integer(oldValue), new Integer(newValue)); } } public int getPrice() { return price; } } Bemerkungen: • In der Regel ist ein Constrained Property auch ein Bound Property, damit die Listener auch über den positiven Ausgang der Abstimmung unterrichtet werden können. // beans/VetoableChangeListener.java import import import import java.beans.PropertyChangeEvent; java.beans.PropertyChangeListener; java.beans.VetoableChangeListener; java.beans.PropertyVetoException; public class VetoableBeanListener implements VetoableChangeListener, PropertyChangeListener { private VetoableBean vetoBean; public VetoableBeanListener(VetoableBean vb) { vetoBean = vb; vetoBean.addVetoableChangeListener(this); vetoBean.addPropertyChangeListener(this); } public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException { if (e.getSource() == vetoBean && e.getPropertyName() == VetoableBean.PRICE) { int oldValue = ((Integer) e.getOldValue()).intValue(); int newValue = ((Integer) e.getNewValue()).intValue(); int d = oldValue * 5 / 100; if (newValue < oldValue - d || newValue > oldValue + d) { throw new PropertyVetoException("change > 5 percent", e); } } } public void propertyChange(PropertyChangeEvent e) { if (e.getSource() == vetoBean && e.getPropertyName() == VetoableBean.PRICE) { 204 KAPITEL 10. JAVA BEANS System.out.println("New price: " + e.getNewValue()); } } } Der typische Ablauf ist in folgendem Sequenzdiagramm dargestellt. :Sender setNumber :VetoableBean addVetoableChangeListener addPropertyChangeListener vetoableChange propertyChange :Listener Abbildung 10.3: Sequenzdiagramm für Vetoable Properties 10.2 Explizite Beschreibung einer Bean Will man sich nicht auf die automatische Erkennung der Fähigkeiten einer Bean verlassen und stattdessen explizit die Schnittstelle einer Bean beschreiben, dann kann man eine Klasse bereitstellen, die das BeanInfo Interface implementiert. Zur Vereinfachung gibt es eine vordefinierte Klasse SimpleBeanInfo, die das BeanInfo Interface implementiert und von der man eine Klasse ableiten kann. • Die Beschreibung einer Bean und ihrer Properties, Events und Methoden erfolgt durch Descriptoren, die von der Klasse FeatureDescriptor abgeleitet sind. • Zusätzlich kann ein Icon in unterschiedlichen Größen zur Darstellung in Werkzeugen bereitgestellt werden. • Wird statt eines Descriptors der Wert null zurückgeliefert, so wird sich ein Werkzeug Informationen über die Bean mit Hilfe des Reflection-Mechanismums besorgen. 205 10.2. EXPLIZITE BESCHREIBUNG EINER BEAN «interface» BeanInfo +getIcon(iconKind:int): Image +getBeanDescriptor(): BeanDescriptor +getPropertyDescriptor(): PropertyDescriptor[] +getMethodDescriptor(): MethodDescriptor[] +getEventSetDescriptor(): EventSetDescriptor[] SimpleBeanInfo BeanDescriptor Object PropertyDescriptor FeatureDescriptor EventSetDescriptor MethodDescriptor ParameterDescriptor Abbildung 10.4: Interface BeanInfo und unterstützende Klassen // beans/VetoableBeanBeanInfo.java import import import import import import java.beans.PropertyDescriptor; java.beans.EventSetDescriptor; java.beans.SimpleBeanInfo; java.beans.IntrospectionException; java.beans.PropertyChangeListener; java.beans.VetoableChangeListener; public class VetoableBeanBeanInfo extends SimpleBeanInfo { private static PropertyDescriptor[] properties; private static EventSetDescriptor[] events; static { try { PropertyDescriptor price = new PropertyDescriptor("price", VetoableBean.class, "getPrice", "setPrice"); properties = new PropertyDescriptor[] { price }; } catch (IntrospectionException e) { e.printStackTrace(); } try { EventSetDescriptor pce = new EventSetDescriptor(VetoableBean.class, "propertyChange", PropertyChangeListener.class, new String[] { "propertyChanged" }, "addPropertyChangeListener", "removePropertyChangeListener"); 206 KAPITEL 10. JAVA BEANS EventSetDescriptor vce = new EventSetDescriptor(VetoableBean.class, "vetoableChange", VetoableChangeListener.class, new String[] { "vetoableChange" }, "addVetoableChangeListener", "removeVetoableChangeListener"); events = new EventSetDescriptor[] { pce, vce }; } catch (IntrospectionException e) { e.printStackTrace(); } } public PropertyDescriptor[] getPropertyDescriptors() { return properties; } public EventSetDescriptor[] getEventSetDescriptors() { return events; } public java.awt.Image getIcon(int iconKind) { java.awt.Image img = null; if (iconKind == ICON_COLOR_16x16 || iconKind == ICON_MONO_16x16) { img = loadImage("price16.gif"); } if (iconKind == ICON_COLOR_32x32 || iconKind == ICON_MONO_32x32) { img = loadImage("price32.gif"); } return img; } } 10.3 Verpacken einer Bean Beans werden typischerweise in einem Java Archiv (.jar) verpackt in dem auch zusätzliche Hilfsklassen (z.B. BeanInfo) enthalten sein können. Eine spezielle Manifest-Datei beschreibt den Aufbau eines Archivs und wird bei der Erstellung des Archivs automatisch erzeugt. Bei Archiven, die Beans enthalten, muss eine Vorlage (.mf) für diese Manifest-Datei geschrieben werden, mit der weitere Meta-Informationen über die Beans in einem Archiv den Werkzeugen bekannt gemacht werden. Die Manifest-Datei besteht aus einer Folge von Zeilen. In jeder Zeile befindet sich genau ein SchlüsselWert-Paar. Der Schlüssel wird durch einen Doppelpunkt und folgende Leerzeichen vom Wert getrennt. Zusammengehörende Schlüssel-Wert-Paare stehen in aufeinanderfolgenden Zeilen. Leerzeilen trennen Gruppen von Schlüssel-Wert-Paaren. Ein einfaches Manifest für die VetoableBean sieht etwa folgendermaßen aus: Manifest-Version: 1.0 Name: VetoableBean 10.4. BEISPIEL 207 Java-Bean: True Name: VetoableBeanBeanInfo Depends-On: price16.gif Depends-On: price32.gif Design-Time-Only: True Bemerkungen: • Durch Manifest-Version wird die Version des Manifestformats beschrieben (1.0 für Java 2). • Die Klasse VetoableBean wird durch Java-Bean als Bean kenntlich gemacht. • Mit Depends-On können Abhängigkeiten beschrieben werden. • Ein Design-Time-Only zeigt an, dass ein Element im Archiv nur während des Entwurfs benötigt wird und beim Erstellen der fertigen Anwendung weggelassen werden kann. 10.4 Beispiel Hier folgt nun ein Beispiel für ein Bean, die einen Timer realisiert, der periodisch ein ActionEvent generiert, das von einem TimerListener abgefangen werden kann. Zunächst das TimerListener Interface: // beans/TimerListener.java public interface TimerListener extends java.util.EventListener { public void onTime(java.awt.event.ActionEvent event); } Die Implementation der Timer Bean: // beans/Timer.java import import import import import java.awt.event.ActionEvent; java.beans.PropertyChangeSupport; java.beans.PropertyChangeListener; java.util.Vector; java.util.Enumeration; public class Timer extends Object implements java.io.Serializable { public static final String PROP_DELAY = "delay"; public static final long DEFAULT_DELAY = 1000; transient private TimerThread timerThread; transient private Vector listeners; private PropertyChangeSupport propertySupport; private boolean running; private long delay; 208 KAPITEL 10. JAVA BEANS public Timer() { delay = DEFAULT_DELAY; propertySupport = new PropertyChangeSupport(this); start(); } public synchronized void start() { if (running) return; timerThread = new TimerThread(); running = true; timerThread.start(); } public synchronized void stop() { if (! running) return; timerThread.stop(); timerThread = null; running = false; } // deprecated public long getDelay() { return delay; } public void setDelay(long value) { if (delay == value) return; long oldValue = delay; delay = value; propertySupport.firePropertyChange(PROP_DELAY, new Long(oldValue), new Long(delay)); } public void addPropertyChangeListener(PropertyChangeListener pcl) { propertySupport.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { propertySupport.removePropertyChangeListener(pcl); } public void addTimerListener(TimerListener l) { if (listeners == null) listeners = new Vector(); listeners.addElement(l); } 10.4. BEISPIEL public void removeTimerListener(TimerListener l) { if (listeners == null) return; listeners.removeElement(l); } private void fireTimerEvent() { if (listeners == null) return; Vector l; synchronized(this) { l = (Vector)listeners.clone(); } for (Enumeration e = l.elements(); e.hasMoreElements();) { TimerListener tl = (TimerListener) e.nextElement(); tl.onTime(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "onTime")); } } class TimerThread extends Thread { public void run() { while (true) { try { sleep(delay); } catch (InterruptedException e) {} fireTimerEvent(); } } } } Zur Vollständigkeit nun noch die Implementation der Klasse TimerBeanInfo: // beans/TimerBeanInfo.java import import import import import import import java.beans.EventSetDescriptor; java.beans.PropertyDescriptor; java.beans.SimpleBeanInfo; java.beans.IntrospectionException; java.awt.Image; java.awt.event.ActionEvent; java.lang.reflect.Method; public class TimerBeanInfo extends SimpleBeanInfo { private Image icon; private static PropertyDescriptor[] properties; private static EventSetDescriptor[] events; static { 209 210 KAPITEL 10. JAVA BEANS try { PropertyDescriptor delay = new PropertyDescriptor("Delay", Timer.class, "getDelay", "setDelay"); properties = new PropertyDescriptor[] { delay }; Method listenerMethod = TimerListener.class.getMethod("onTime", new Class[] { ActionEvent.class } ); Method addListenerMethod = Timer.class.getMethod("addTimerListener", new Class[] { TimerListener.class } ); Method removeListenerMethod = Timer.class.getMethod("removeTimerListener", new Class[] { TimerListener.class } ); EventSetDescriptor timer = new EventSetDescriptor("timer", TimerListener.class, new Method[] { listenerMethod }, addListenerMethod, removeListenerMethod); events = new EventSetDescriptor[] { timer }; } catch (IntrospectionException ex) { ex.printStackTrace(); } catch (NoSuchMethodException ex) { ex.printStackTrace(); } } public TimerBeanInfo() { icon = loadImage("timer.gif"); } public Image getIcon(int type) { return icon; } public PropertyDescriptor[] getPropertyDescriptors() { return properties; } public EventSetDescriptor[] getEventSetDescriptors() { return events; } } Kapitel 11 Servlets Das World-Wide-Web spielt eine zunehmend wichtige Rolle bei der internen Arbeitsorganisation und der Abwicklung von Transaktionen zwischen Unternehmungen. Entsprechend müssen viele Anwendungen heutzutage Web-basierte Benutzungsoberflächen bereitstellen. In diesem Kapitel wird daher beschrieben, wie mit Hilfe von Java Servlets Programme in einem Web-Server ausgeführt werden können um dynamisch Web-Seiten zu generieren und Interaktionen zu verfolgen. 11.1 Grundlagen der Web-Technologie Zunächst folgt eine kurze Zusammenfassung der technischen Grundlagen des World-Wide-Webs. Eine genaue Diskussion der einzelnen relevanten Protokolle und Standards kann an dieser Stelle nicht erfolgen. Bei Bedarf nach weiteren Informationen sei an dieser Stelle auf die angegebenen Quellen verwiesen. 11.1.1 URIs, URLs und URNs Globale Namensräume zur Adressierung und Benennung von Ressourcen: • URI: Uniform Resource Identifier (Oberbegriff für URLs und URNs, RFC 2396) • URL: Uniform Resource Locator (ortsgebundene Adresse einer Ressource) • URN: Uniform Resource Name (persistenter Name einer Ressource) URIs können Parameter haben, die den Zugriff auf eine Ressource näher beschreiben. 11.1.2 MIME Die MIME-Standards (Multipurpose Internet Mail Extension) sind zur Übertragung von verschiedenen Datenformaten in Internet Mails entstanden (RFC 2045-2049). Das Grundprinzip ist die Identifikation eines Inhalts (content) durch einen Inhaltstyp (content-type), seiner Länge (content-length) und Trennkennzeichen. 11.1.3 HTTP Das Hypertext Transfer Protocol (HTTP) ist in der Version 1.1 in RFC 2616 spezifiziert und stellt die folgenden HTTP Methoden bereit: 211 212 KAPITEL 11. SERVLETS • GET: Lesen einer Ressource • HEAD: Lesen von Meta-Informationen über einer Ressource • POST: Erweitern einer existierenden Ressource • PUT: Schreiben einer Ressource • DELETE: Löschen einer Ressource • TRACE: Testaufruf zum Lokalisieren von Fehlern • OPTIONS: Informationen über verfügbare Optionen. Die HTTP 1.1 Statuscodes sind strukturiert aufgebaut: • 1xx: informational • 2xx: successful • 3xx: redirection • 4xx: client error • 5xx: server error Jeder HTTP-Methodenaufruf enthält eine Menge von Headern, die zusätzliche Informationen über den Aufruf beinhalten können oder Informationen über die Benutzer bzw. die verwendetet Software enthalten. 11.1.4 HTML Die Hypertext Markup Language ist die Seitenbeschreibungssprache. Sie stammt ursprünglich von SGML ab, einem mächtigen Standard zur Beschreibung von Dokumentstrukturen. Mittlerweile hat man einen etwas weniger mächtigen und damit auch weniger komplexen Standard zur Beschreibung von Dokumentstrukturen geschaffen, die Extensible Markup Language (XML). Man hat auf der Basis von XML HTML neu definiert, was zur aktuellen Version XHTML führt (die derzeit leider nicht von allen Browsern korrekt interpretiert wird). 11.2 Generische Servlets Servlets sind Java Programme, die von einem Web-Server beim Zugriff auf bestimmte URLs ausgeführt werden und in der Regel dynamisch HTML-Seiten erzeugen. Die Ausführung passiert in einer sogenannten Servlet-Engine, die entweder direkt im Web-Server integriert ist oder als externer Prozess realisiert wird und mit dem Web-Server kommuniziert. // servlets/HelloWorldServlet.java import import import import import import java.io.PrintWriter; java.io.IOException; javax.servlet.GenericServlet; javax.servlet.ServletRequest; javax.servlet.ServletResponse; javax.servlet.ServletException; public class HelloWorldServlet extends GenericServlet { static final String DOC = "-//W3C//DTD XHTML 1.0 Strict//EN"; 11.2. GENERISCHE SERVLETS 213 static final String DTD = "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd"; public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println("<!DOCTYPE html PUBLIC \"" + DOC +"\""); out.println("\"" + DTD + "\">"); out.println("<html lang=\"en\">"); out.println(" <head>"); out.println(" <title>HelloWorldServlet</title>"); out.println(" </head>"); out.println(" <body>"); out.println(" <p>Hello World!</p>"); out.println(" <hr/>"); out.println(" <p>Generated: " + (new java.util.Date()).toString() + "</p>"); out.println(" </body>"); out.println("</html>"); } } Die Anfrage wird durch das ServletRequest Interface dargestellt. • Die einzelnen Anfrageparameter werden über eine Enumeration zur Verfügung gestellt. • Auf das in einer Anfrage enthaltene Dokument kann über einen ServletInputStream zugegriffen werden, von dem man sich mit getReader() einen BufferedReader besorgen kann. • Weitergehende Informationen über den erfolgten Zugriff kann man über den Aufruf weiterer Hilfsmethoden des ServletRequest Objekts erhalten. Die Antwort wird analog durch das ServletResponse Interface beschrieben. • Im Antwortobjekt kann man den MIME-Typ der Antwort bestimmen und ggf. explizit die Länge der Antwort (content) setzen. • Das zur Antwort gehörende Dokument wird in einen ServletOutputStream geschrieben, von dem man sich mit getWriter() einen PrintWriter besorgen kann. // servlet/ParameterServlet.java import import import import import import import java.util.Enumeration; java.io.PrintWriter; java.io.IOException; javax.servlet.GenericServlet; javax.servlet.ServletRequest; javax.servlet.ServletResponse; javax.servlet.ServletException; public class ParameterServlet extends GenericServlet { public void service(ServletRequest req, ServletResponse res) 214 KAPITEL 11. SERVLETS throws ServletException, IOException { res.setContentType("text/plain"); PrintWriter out = res.getWriter(); // Every ServletRequest gives us ... out.println("Server:\t" + req.getServerName()); out.println("Port:\t" + req.getServerPort()); out.println("Client:\t" + req.getRemoteHost()); out.println("Protocol:" + req.getProtocol()); out.println("Scheme:\t" + req.getScheme()); out.println("Security:\t" + req.isSecure()); out.println("ContentType:\t" + req.getContentType()); out.println("ContentLength:\t" + req.getContentLength()); // ... plus all the request parameters: out.println(""); Enumeration e = req.getParameterNames(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); String values[] = req.getParameterValues(name); out.print("Parameter " + name + ":\t"); for (int i = 0; i < values.length; i++) { out.print(values[i] + " "); } out.println(); } } } 11.3 HTTP Servlets Da HTTP eine so große Bedeutung hat, gibt es eine entsprechende Spezialisierung der Anfrage- und Antwortschnittstellen und eine Klasse HttpServlet, die Methoden für die einzelnen HTTP-Methoden bereitstellt. // servlet/HeaderServlet.java import import import import import import import java.util.Enumeration; java.io.PrintWriter; java.io.IOException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; javax.servlet.ServletException; public class HeaderServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/plain"); 215 11.3. HTTP SERVLETS PrintWriter out = res.getWriter(); // Every ServletRequest gives us ... out.println("Server:\t" + req.getServerName()); out.println("Port:\t" + req.getServerPort()); out.println("Client:\t" + req.getRemoteHost()); out.println("Protocol:" + req.getProtocol()); out.println("Scheme:\t" + req.getScheme()); out.println("Security:\t" + req.isSecure()); out.println("ContentType:\t" + req.getContentType()); out.println("ContentLength:\t" + req.getContentLength()); // ... and an HttpServletRequest tells us even more ... out.println(""); out.println("Method:\t" + req.getMethod()); out.println("URI:\t" + req.getRequestURI()); out.println("Path:\t" + req.getServletPath()); // ... plus all the HTTP header information. out.println(""); Enumeration e = req.getHeaderNames(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); String value = req.getHeader(name); out.println("Header " + name + ":\t" + value); } } } Die wichtigsten Klassen und Interfaces sind in folgendem UML Klassendiagramm dargestellt: «interface» «interface» «interface» Servlet ServletRequest ServletResponse +init(config:ServletConfig): void +destroy(): void +service(req:ServletRequest,res:ServletResponse): void «interface» «interface» «interface» HttpServletRequest HttpServletResponse JspPage HttpJspPage Object GenericServlet HttpServlet #doGet(req:HttpServletRequest,res:HttpServletResponse): void #doPost(req:HttpServletRequest,res:HttpServletResponse): void #doPut(req:HttpServletRequest,res:HttpServletResponse): void #doDelete(req:HttpServletRequest,res:HttpServletResponse): void #doOptions(req:HttpServletRequest,res:HttpServletResponse): void #doTrace(req:HttpServletRequest,res:HttpServletResponse): void Abbildung 11.1: Java Servlet Klassen und Interfaces Bemerkungen: 216 KAPITEL 11. SERVLETS • Die abstrakte Klasse HttpServlet implementiert die Methode service indem die jeweils zur HTTP-Methode passende Java Methode aufgerufen wird. • Das HttpServletResponse Interface stellt Methoden zum Senden von HTTP Fehlermeldungen (sendError()) und spezielle HTTP Konstanten bereit. • Mit setHeader() und setStatus() können spezielle Header erzeugt werden und der Status eines Dokuments kann gesetzt werden. 11.4 Lebenszyklus und Container Der Lebenszyklus eines Servlets besteht aus drei Phasen: 1. Das Servlet wird in die Ausführungsumgebung geladen. Dabei ruft die Ausführungsumgebung die init() Methode auf, damit sich das Servlet initialisieren kann. 2. Das Servlet bearbeitet Anfragen. 3. Das Servlet wird aus dem Server entfernt. Dabei wird die Methode destroy() von der Ausführungsumgebung aufgerufen. Achtung: Die Ausführungsumgebungen haben in der Regel mehrere Threads. Daher ist eine korrekte Synchronisation erforderlich, auch bei der Terminierung eines Servlets in der destroy Methode. 11.5 Sessions In der Regel ist es notwendig, dass sich Servlets zwischen einzelnen Anfragen Zustandsinformationen merken können. Mit Hilfe des HttpSession Interfaces lässt sich das relativ einfach implementieren. // servler/SessionServlet.java import import import import import import import import java.util.Enumeration; java.io.PrintWriter; java.io.IOException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; javax.servlet.http.HttpSession; javax.servlet.ServletException; public class SessionServlet extends HttpServlet { static final String COUNTER = "counter"; public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { HttpSession session = req.getSession(true); Integer counter = (Integer) session.getAttribute(COUNTER); if (counter == null) { counter = new Integer(0); 11.6. JAVASERVER PAGES (JSP) 217 } counter = new Integer(counter.intValue() + 1); session.setAttribute(COUNTER, counter); res.setContentType("text/plain"); PrintWriter out = res.getWriter(); out.println("Page Counter: " + counter); } } Bemerkungen: • Zu jeder Session gehört eine Menge von Attributen, wobei jedes Attribut ein Name/Wert-Paar ist. • Eine Session hat ein Erzeugungsdatum und eine Lebenszeit nach der die Session bei Inaktivität des Benutzers gelöscht wird. • Die Attribute einer Session werden lokal auf dem Server gehalten. Zur Identifikation einer Session können verschiedene Mechanismen benutzt werden (Cookies, Parameter in URLs). Achtung: Verfahren, die die URLs modifizieren, funktionieren nur, wenn man URLs mit Hilfe der API dynamisch erzeugt und niemals als Konstanten definiert. • Das HttpSessionBindingListener Interface ermöglicht es anderen Objekten, sich an ein Session-Objekt zu binden. Gebundene Objekte werden über Änderungen im Session-Objekt mit dem Ereignis HttpSessionBindingEvent benachrichtigt. 11.6 JavaServer Pages (JSP) Bei der Programmierung von Servlets ist es lästig HTML Dokumente mit Hilfe von Java Methodenaufrufen zu erzeugen und die Wartung der Seiten etwa bei einfachen Layout-Änderungen erzeugt Programmänderungen, die ihrerseits wiederum Fehler in ein ansonsten stabiles Servlet einbringen können. Mit JavaServer Pages (JSP) verfolgt man einen anderen Ansatz, bei dem Java Quelltextfragmente in HTML Dokumente eingebettet werden. Am deutlichsten lässt sich der Unterschied an einem kleinen Beispiel demonstrieren: // servlets/HelloWorldServlet.java import import import import import import java.io.PrintWriter; java.io.IOException; javax.servlet.GenericServlet; javax.servlet.ServletRequest; javax.servlet.ServletResponse; javax.servlet.ServletException; public class HelloWorldServlet extends GenericServlet { static final String DOC = "-//W3C//DTD XHTML 1.0 Strict//EN"; static final String DTD = "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd"; public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); 218 KAPITEL 11. SERVLETS out.println("<!DOCTYPE html PUBLIC \"" + DOC +"\""); out.println("\"" + DTD + "\">"); out.println("<html lang=\"en\">"); out.println(" <head>"); out.println(" <title>HelloWorldServlet</title>"); out.println(" </head>"); out.println(" <body>"); out.println(" <p>Hello World!</p>"); out.println(" <hr/>"); out.println(" <p>Generated: " + (new java.util.Date()).toString() + "</p>"); out.println(" </body>"); out.println("</html>"); } } Dieses Servlet erzeugt eine Seite, auf der das Erzeugungsdatum dynamisch eingetragen wird. Die eigentliche Logik besteht aus genau einer Zeile Java-Quelltext. Mit Hilfe von JSP lässt sich dieselbe Seite folgendermaßen erzeugen: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd"> <%@ page session="false" %> <html lang="en"> <head> <title>HelloWorldJsp</title> </head> <body> <p>Hello World!</p> <hr/> <p>Generated: <%= new java.util.Date() %></p> </body> </html> Bemerkungen: • Offensichtlich handelt es sich bei JSPs um HTML-Dokumente, wobei in speziellen Elementen JavaQuelltext eingebettet sind. • Der HTML-Teil von JSPs kann in der Regel mit normalen HTML-Werkzeugen bearbeitet werden. • Es ist die Aufgabe des Web-Servers, aus JSPs entsprechende Servlets zu generieren, diese zu übersetzen, in den Server zu laden und auszuführen. Es existieren verschiedene JSP-spezifische Konstrukte. Leider ist die originale Syntax ad-hoc und nicht XML-konform. Mittlerweile gibt es eine Spezifikation, die auch XML-konforme Konstrukte anbietet, die aber nicht mit der alten Notation gemischt werden dürfen. Leider unterstützen zur Zeit noch nicht alle JSPImplementationen die neue XML-konforme Syntax. 11.6. JAVASERVER PAGES (JSP) 11.6.1 219 Deklarationen In Deklarationen können Variablen oder Methoden deklariert werden, die später benutzt werden sollen. Es ist notwendig, Variablen und Methoden vor der Benutzung zu deklarieren. Originale JSP-Notation: <%! _declaration_; [_declaration_;]+ %> XML-Notation: <jsp:declaration> _declaration_; [_declaration_;]+ </jsp:declaration> Hierbei steht declaration für eine gültige Java Deklaration. Deklarationen können auch aus anderen Dateien statisch eingebunden werden. 11.6.2 Ausdrücke Ein Ausdruck ist eine Java Ausdruck, der in eine Zeichenkette umgewandelt wird und anstelle des JSPAusdrucks in das HTML Dokument eingefügt wird. Originale JSP-Notation: <%= _expression_ %> XML-Notation: <jsp:expression> _expression_ </jsp:expression> 11.6.3 Scriptlets Ein Scriptlet beinhaltet Quelltext-Fragment bestehend aus einer beliebigen Menge von Deklarationen, Statements oder Ausdrücken. Originale JSP-Notation: <% _code_fragment_ %> XML-Notation: <jsp:scriptlet> _code_fragment_ </jsp:scriptlet> Scriptlets werden bei der Bearbeitung von Anfragen ausgeführt. Ein PrintWriter mit dem Namen out wird zur Verfügung gestellt, um Ausgaben an der Position des Scriptlets in das HTML Dokument einzufügen. 220 KAPITEL 11. SERVLETS 11.6.4 Einfügungen In eine JSP-Datei kann der Inhalt anderer JSP-Dateien eingefügt werden. Originale JSP-Notation: <%@ include file="_relativeURL_" %> XML-Notation: <jsp:directive_include file="_relativeURL_"/> Das Einbinden der Datei relativeURL in die aktuelle JSP Datei erfolgt zum Zeitpunkt der Übersetzung (static include). Die eingefügte Datei kann selbst wieder JSP-Konstrukte enthalten. 11.6.5 Weiterleitungen Eine JSP-Datei kann die Bearbeitung der Anfrage an andere JSP-Dateien oder statische HTML-Dokumente weiterleiten. XML-Notation: <jsp:forward page="_relativeURL_" /> Die relative URL relativeURL kann auf dynamische oder statische Dokumente verweisen. Bei Verweisen auf dynamische Dokumente können Parameter in der Form von Attribut/Wert-Paaren weitergereicht werden. 11.6.6 Seiten-Direktiven Seiten-Direktiven setzen Attribute, die für die gesamten JSP-Seite gelten. Originale JSP-Notation: <%@ page _directive_="_value_" %> XML-Notation: <jsp:directive_page _page_directive_list_ /> Die wichtigsten Seiten-Direktiven sind: • import=" package.class " Diese Direktive importiert Klassen oder Pakete und kann mehrmals in einer JSP-Seite auftauchen. Die Pakete java.lang.*, javax.servlet.*, javax.servlet.jsp.* und javax.servlet.http.* werden automatisch importiert. • session=" bool " Der boolesche Wert zeigt an, ob für die JSP-Seite eine Session benötigt wird oder nicht. Wenn der Wert true ist, dann ist die Session über das Objekt mit dem Namen session zugreifbar. 11.7. JAVASERVER PAGES UND BEANS 221 • info=" text " Ein erläuternder statischer Text wird im erzeugten Servlet abgelegt. Auf den Text kann mit Hilfe der Methode Servlet.getServletInfo() zugegriffen werden. • contentType=" mimetype " Bestimmt den erzeugten MIME-Typ, standardmäßig text/html. • isThreadSafe=" bool " Bei einer sicheren JSP-Seite können mehrere Threads gleichzeitig nebenläufige Zugriffe auf die Seite abarbeiten. Setzt man diese Direktive auf false, so wird der Web-Server die einzelnen Threads serialisieren. • errorPage=" relativeURL " Ein Verweis auf eine andere Seite, die die Behandlung von Fehlern übernimmt. 11.7 JavaServer Pages und Beans Die Einbettung von Java-Quelltext in HTML-Dokumente schafft leider immer noch keine befriedigende Trennung der Darstellung (HTML) von der zugrundeliegenden Verarbeitungslogik (Java). Man kann allerdings durch die Verwendung von Beans die Trennung deutlich verbessern. Es gibt entsprechende JSPKonstrukte, die insbesondere die Verarbeitung von Daten aus Web-Formularen vereinfachen helfen. 11.7.1 Bean-Benutzung Durch die Benutzung von Beans lässt sich der Umfang von Java-Quelltext in HTML-Dokumenten deutlich reduzieren. Außerdem lässt sich die Übergabe von Parametern aus HTML-Formularen and Java-Programme vereinfachen, da Parameter automatisch an Properties von Beans zugewiesen werden können. XML-Notation: <jsp:useBean id="_bean_" scope="_scope_" class="_package.class_" /> Bemerkungen: • id=" bean " Definiert den Namen einer Variablen, über die auf die Instanz der Bean zugegriffen werden kann. • scope=" scope " Definiert, wo aus die Instanz der Bean zugegriffen werden kann. Der Parameter scope kann folgende vordefinierte Werte annehmen: – page: Gültigkeit in der JSP-Seite, die auch das <jsp:useBean/> Konstrukt enthält. – request: Gültigkeit für beliebige JSP-Seiten bis zur Beendigung der aktuellen Anfrage. – session: Gültigkeit für alle JSP-Seiten, die dieselbe Session bearbeiten. – application: Gültigkeit für alle JSP-Seiten einer ganze Applikation (was immer eine Applikation genau ist). • class=" package.class " Spezifiziert die Klasse, die die Bean implementiert. Die Klasse muss einen public Konstruktor ohne Argumente bereitstellen. 222 KAPITEL 11. SERVLETS 11.7.2 Properties Auf die nicht indizierten Properties einer Bean kann über spezielle JSP-Konstrukte zugegriffen werden. XML-Notation: <jsp:getProperty <jsp:setProperty <jsp:setProperty <jsp:setProperty name="_bean_" name="_bean_" name="_bean_" name="_bean_" property="_name_" /> property="_prop_" value="_val_" /> property="_prop_" param="_par_" /> property="*" /> Bemerkungen: • Mit getProperty wird ein Wert einer Bean Property ausgelesen. • Die erste Form von setProperty weist den Wert val der Property prop der Bean bean zu. Die zweite Form weist den Wert des Anfrageparameters par zu während die dritte Form alle Parameter der Anfrage entsprechenden Properties zuweist. Bei der Zuweisung werden die Zeichenketten automatisch in passende Datentypen konvertiert. Achtung: Die Zuweisung der Properties in der dritten Form von setProperty erfolgt in einer nicht weiter spezifizierten Reihenfolge. 11.8 Beispiel: Raten von Nummern Der folgende Code implementiert eine einfache Bean zum Raten von Zahlen. // servlets GuessNumberBean.java public class GuessNumberBean implements java.io.Serializable { private int number; // number to guess private int guess; // current guess private int count; // number of guesses so far private String hint; // hint for next guess private boolean correct; // correct guess? public GuessNumberBean() { reset(); } public void reset() { number = Math.abs(new java.util.Random().nextInt() % 100) + 1; correct = false; guess = -1; count = 0; hint = ""; } public void setGuess(String value) { 11.8. BEISPIEL: RATEN VON NUMMERN count++; try { guess = Integer.parseInt(value); } catch (NumberFormatException e) { guess = -1; hint = "a number next time"; return; } correct = (guess == number); if (! correct) { hint = (guess < number) ? "higher" : "lower"; } } public String getGuess() { return "" + guess; } public boolean isCorrect() { return correct; } public String getHint() { return hint; } public int getCount() { return count; } } Die Integration in das Web erfolgt durch eine JSP: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd"> <%@ page import="GuessNumberBean" %> <jsp:useBean id="gnb" class="GuessNumberBean" scope="session"/> <jsp:setProperty name="gnb" property="*"/> <html lang="en"> <head> <title>Guess Numbers</title> </head> <body bgcolor="white"> <% if (gnb.isCorrect()) { %> <p> Congratulations! You found the correct number <%= gnb.getGuess() %> after just 223 224 KAPITEL 11. SERVLETS <%= gnb.getCount() %> tries. </p> <p> <% gnb.reset(); %> <a href="GuessNumber.jsp">Try again</a>? </p> <% } else if (gnb.getCount() == 0) { %> <p>Welcome to the Guess Number game.</p> <p>I am thinking of a number between 1 and 100.</p> <form method="get"> Your guess: <input type="text" name="guess"> <input type="submit" value="submit"> </form> <% } else { %> <p>Your guess <%= gnb.getGuess() %> is good, but not quite correct. Try <%= gnb.getHint() %>.</p> <p>You have made <%= gnb.getCount() %> guesses so far.</p> <form method="get"> Your guess: <input type="text" name="guess"> <input type="submit" value="submit"> </form> <% } %> </body> </html> 11.9 Tomcat Referenzimplementation Im Rahmen des Jakarta-Projekts ist die Tomcat Referenzimplementation entstanden. • Tomcat kann sowohl eigenständig betrieben werden oder in Kombination mit anderen Web-Servern wie z.B. Apache. • Komplexe Web-basierte Applikationen können in sogenannte Web Application Resource (WAR) Dateien gepackt werden und damit relativ einfach verteilt und installiert werden. • Tomcat unterstützt auch die Standards zur verteilten Erstellung und Pflege von Web-Dokumenten (WebDAV). Kapitel 12 Extensible Markup Language (XML) Die Extensible Markup Language (XML) [4] ist eine Sprache zur Beschreibung von strukturierten Dokumenten. XML ist aus der Standard Generalized Markup Language (SGML) [13] hervorgegangen, die ursprünglich von IBM entwickelt und später von der ISO standardisiert wurde. Obwohl XML ursprünglich als Mechanismus zur Beschreibung von strukturierten Dokumenten entwickelt wurde, findet XML zur Zeit auch generell zur Darstellung von strukturierten Daten insbesondere zum Austausch zwischen Applikationen Verwendung. 12.1 Struktur von XML Dokumenten Jedes XML-Dokument besitzt eine Baumstruktur und besteht im wesentlichen aus den folgenden Strukturelementen: • Ein ElementNode ist ein Strukturelement mit einem Namen, mit dem die Struktur des Baums aufgespannt wird. Die syntaktische Darstellung eines ElementNode mit dem Namen person geschieht durch zwei Tags. <person></person> Zwischen den Tags können weitere Strukturelemente vorhanden sein. <person> <name></name> </person> Enthält ein ElementNode keine anderen Strukturelemente, dann kann die syntaktische Darstellung verkürzt werden: <person/> • Ein TextNode ist ein Strukturelement, das Text enthält. Texte können in unterschiedlichen Zeichensätzen abgelegt werden. Jede Implementation muß UTF8 und UTF16 unterstützen. Das folgende Beispiel zeigt einen ElementNode, der einen TextNode enthält. <email>[email protected]</email> • Ein CommentNode ist ein Strukturelement, das einen Kommentar enthält. Die syntaktische Darstellung erfolgt nach folgendem Muster: <!-- This is a comment. --> 225 226 KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML) Aufgrund der syntaktischen Struktur ist klar, dass Kommentare nicht geschachtelt werden können. • Ein EntityNode ist ein Strukturelment, das durch eine Ersetzung in einen TextNode verwandelt werden kann. Die EntityNodes sind insbesondere zur Darstellung der XML Meta-Zeichen <, >, ’, " und & notwendig: &lt; &gt; &apos; &quote; &amp; • Ein ProcessingInstructionNode enthält Verarbeitungshinweise, die nicht wirklich Teil der in einem Dokument enthaltenen Daten sind. Die syntaktische Darstellung erfolgt folgendermassen: <?rfc toc?> • Ein AttributeNode wird benutzt, um Attribute des umschliessenden Elements zu definieren. Ein AttributeNode spannt ebenfalls einen Teilbaum auf und besitzt analog zu einem ElementNode einen Namen. Der Wert eines AttributeNodes befindet sich wiederum in einem untergeordneten TextNode. Das folgende Beispiel definiert einen ElementNode mit dem Namen email, einem AttributeNode mit dem Namen category und einen zugheörigen TextNode mit den Inhalt work. <email category="work">[email protected]</email> • Es gibt noch weitere Strukturelemente, die aber hier von geringerer Bedeutung sind. Hier ist ein typisches Beispiel für ein XML-Dokument, dass die wichtigsten Informationen über die Mitarbeiter einer imaginären Organisation beschreibt. <?xml version="1.0"?> <!DOCTYPE staff SYSTEM "staff.dtd"> <staff> <person> <name> <first>Peter</first> <last>Mustermann</last> </name> <email>[email protected]</email> <email category="private">[email protected]</email> <phone category="work">+49 541 969 4242</phone> <phone category="private">+49 541 123 4242</phone> </person> </staff> Die interne Struktur dieses XML-Dokuments kann man sich z.B. mit Hilfe von xmllint anzeigen lassen. Man beachte, dass die Leerzeichen zwischen den XML-Strukturelementen alle durch eigene TextNodes dargestellt werden, was auch den hohen Speicherbedarf zur internen Darstellung von XML-Dokumenten erklärt. DOCUMENT version=1.0 URL=/home/schoenw/xml/staff.xml standalone=true DTD(staff), SYSTEM staff.dtd ELEMENT staff TEXT content= ELEMENT person TEXT content= 12.2. DOCUMENT TYPE DEFINITIONS (DTD) 227 ELEMENT name TEXT content= ELEMENT first TEXT content=Peter TEXT content= ELEMENT last TEXT content=Mustermann TEXT content= TEXT content= ELEMENT email TEXT [email protected] TEXT content= ELEMENT email ATTRIBUTE category TEXT content=private TEXT [email protected] TEXT content= ELEMENT phone ATTRIBUTE category TEXT content=work TEXT content=+49 541 969 4242 TEXT content= ELEMENT phone ATTRIBUTE category TEXT content=private TEXT content=+49 541 123 4242 TEXT content= TEXT content= 12.2 Document Type Definitions (DTD) Die genaue Struktur eines XML-Dokuments kann man mit einer Document Type Definition (DTD) festlegen. Formal definiert eine DTD eine Grammatik, die die Struktur aller XML-Dokumente beschreibt, die gültige Worte der durch die in der DTD definierten Grammatik erzeugten Sprache sind. Das Konzept der DTDs und die Syntax zur Definition von DTDs stammt vom XML-Vorläufer SGML. Das folgende Beispiel definiert die Grammatik für das Beispiel XML-Dokument: <!-- DTD for the staff.xml file --> <!ENTITY % CTEXT "#PCDATA"> <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT <!ELEMENT staff (person*)> person (name, email+, phone+)> name (title?, first, middle?, last)> title (%CTEXT;)> first (%CTEXT;)> middle (%CTEXT;)> last (%CTEXT;)> email (%CTEXT;)> phone (%CTEXT;)> <!ATTLIST email 228 KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML) category (work|private|other) "work"> <!ATTLIST phone category (work|private|other) "work"> Bemerkungen: • Die Definition des Elements staff besagt, dass in diesem Element 0 oder mehrere Elemente person enthalten sein können. • Die Definition des Elements person besagt, dass in diesem Element genau ein Element name vorhanden sein muss, gefolgt von ein oder mehreren email-Elementen, denen wiederum ein oder mehrere phone-Elemente folgen. • Die Definition des Element name legt fest, dass in diesem Element zunächst optional ein titleElement steht, gefolgt von genau einem first-Element, einem optionalen middle-Element und genau einem last-Element. • Die Elemente title, first, middle, last, email und phone beinhalten jeweils einen CTEXT. • Der CTEXT ist wiederum als ein Entity definiert und wird durch den Wert #PCDATA (parsed character data) substitutiert. • Für die Elemente email und phone werden zum Schluss noch die Attribute festgelegt. Beide Elemente haben ein Attribut category, das jeweils einen der Werte work, private und other annehmen kann. Ist das Attribut nicht vorhanden, dann wird der Wert work Standardwert angenommen. XML-Dokumente, die mit der entsprechenden DTD konsistent sind, werden bezüglich der DTD als gültiges (valid) XML-Dokument bezeichnet. Dokumente, die lediglich der syntaktischen Struktur eines XMLDokuments entsprechen, werden all wohlgeformte (well-formed) XML-Dokumente bezeichnet. Wohlgeformtheit ist also eine sehr schwache minimale Forderung die ohne Kenntnis der DTD überprüfbar ist. 12.3 XML Namensräume 12.4 XML Schema (XSD) Obwohl mit DTDs die Struktur eines XML-Dokuments beschrieben werden kann, hat dieser Ansatz einige Schwächen: • Die Ausdrucksfähigkeit der DTDs ist relativ begrenzt. Insbesondere kann nicht formal beschrieben werden, welche Form die Daten in den Text-Elementen haben sollen. Es ist also durchaus möglich in das email-Element eine Telefonnummer einzutragen, ohne dadurch die Gültigkeit der Dokuments verändert wird. • Die Wiederwendung von Definitionen in DTDs ist eingeschränkt. • Die Verarbeitung einer DTD benötigt spezielle Parser, da die DTDs nicht selbst in XML definiert sind. Entsprechend wurde eine auf XML basierende Sprache zur Beschreibung von XML-Dokumenten entwickelt. Diese Sprache hat den Namen XML-Schema bekommen []. <?xml version="1.0"?> <!-- xml/staff.xsd --> 12.4. XML SCHEMA (XSD) <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:per="http://www.inf.uos.de/schoenw/person" xml:lang="en"> <xsd:annotation> <xsd:documentation> This schema defines the formal syntax of the staff structured XML schema type. </xsd:documentation> </xsd:annotation> <!-- This has not been tested since I do not know how to feed multiple schemas into one of the schema validators. --> <xsd:complexType name="staff"> <xsd:sequence> <xsd:element name="person" type="per:person" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:schema> <?xml version="1.0"?> <!-- xml/person.xsd --> <xsd:schema targetNamespace="http://www.inf.uos.de/schoenw/person" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en"> <xsd:annotation> <xsd:documentation> This schema defines the formal syntax of the person structured XML schema type. </xsd:documentation> </xsd:annotation> <!-The following two complex types define the person and name sequences of elements. This is still simple... --> <xsd:complexType name="person"> <xsd:sequence> <xsd:element name="name" type="name"/> <xsd:element name="email" type="email" minOccurs="1" maxOccurs="unbounded"/> <xsd:element name="phone" type="phone" minOccurs="1" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="name"> 229 230 KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML) <xsd:sequence> <xsd:element name="title" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="first" type="xsd:string"/> <xsd:element name="middle" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="last" type="xsd:string"/> </xsd:sequence> </xsd:complexType> <!-The following two types have to be complex since only complex types can have attributes. In fact, we have a complex type which holds a simple value by extending a simple type defined below. --> <xsd:complexType name="email"> <xsd:simpleContent> <xsd:extension base="emailString"> <xsd:attributeGroup ref="categoryAttributeGroup"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> <xsd:complexType name="phone"> <xsd:simpleContent> <xsd:extension base="phoneString"> <xsd:attributeGroup ref="categoryAttributeGroup"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType> <!-These are our simple types for email and phone strings. Regular expressions are used to restrict the set of legal values. --> <xsd:simpleType name="emailString"> <xsd:restriction base="xsd:string"> <!-- <xsd:pattern value=""/> --> </xsd:restriction> </xsd:simpleType> <xsd:simpleType name="phoneString"> <xsd:restriction base="xsd:string"> <xsd:pattern value="\+?[0-9 ]+"/> </xsd:restriction> </xsd:simpleType> <!-The attribute group allows to define the category attribute in one place. See the above reference to categoryAttributeGroup. 12.5. XML PATHS (XPATH) --> <xsd:attributeGroup name="categoryAttributeGroup"> <xsd:attribute name="category"> <xsd:simpleType> <xsd:restriction base="xsd:string"> <xsd:enumeration value="work"/> <xsd:enumeration value="private"/> <xsd:enumeration value="other"/> </xsd:restriction> </xsd:simpleType> </xsd:attribute> </xsd:attributeGroup> </xsd:schema> 12.5 XML Paths (XPATH) 12.6 XML Stylesheet Language (XSL) <?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml" version="1.0"> <!-- style sheet for the staff.xml file --> <xsl:template match="staff"> <table> <xsl:apply-templates select="person"/> </table> </xsl:template> <xsl:template match="person"> <tr> <td><xsl:apply-templates select="name"/></td> <td><xsl:apply-templates select="email"/></td> <td><xsl:apply-templates select="phone"/></td> </tr> </xsl:template> <xsl:template match="name"> <xsl:if test="title"> <xsl:text> </xsl:text> <xsl:value-of select="title"/> </xsl:if> <xsl:value-of select="first"/> <xsl:if test="middle"> <xsl:text> </xsl:text> <xsl:value-of select="middle"/> </xsl:if> <xsl:text> </xsl:text> <xsl:value-of select="last"/> 231 232 KAPITEL 12. EXTENSIBLE MARKUP LANGUAGE (XML) </xsl:template> <xsl:template match="email"> <xsl:apply-templates/> <xsl:text> </xsl:text> </xsl:template> <xsl:template match="phone"> <xsl:apply-templates/> <xsl:text> </xsl:text> </xsl:template> </xsl:stylesheet> 12.7 Document Object Model (DOM) 12.8 Java DOM API Kapitel 13 Verteilte Anwendungen Fast alle modernen Anwendungen sind mehr oder weniger stark so konzipiert, dass Teile der Anwendungslogik, der Datenhaltung oder der Benutzungsschnittstelle auf verschiedenen vernetzten Rechnern ablaufen können. Man erhofft sich durch die Verteilung insbesondere Vorteile hinsichtlich der Skalierbarkeit. Oftmals ist aber auch der einfache Wunsch nach Wiederverwendung von existierenden spezialisierten Komponenten mit wohl definierten Schnittstellen ein Grund Anwendungen als verteilte Anwendungen zu realisieren. 13.1 Client/Server-Modell Das Client/Server-Modell ist ein grundlegenden Kommunikationsmodell, bei dem die Kommunikationspartner in einem System entweder die Rolle des Clients oder des Servers einnehmen. Ein Server verwaltet bestimmte Ressourcen während ein Client die Ressourcen nutzen möchte. Client Server Access Result Abbildung 13.1: Client/Server-Modell Der Kommunikationsablauf im Client/Server-Modell besteht in der Regel aus zwei Nachrichten. Während der Verarbeitung des Zugriffs ist im einfachsten Fall der Client blockiert. Client/Server-System lassen sich elegant mit der Hilfe von entfernten Proceduraufrufen (remote procedure calls, RPC) implementieren. Beim RPC wird eine lokale Funktion aufgerufen, die als Stub-Funktion den eigentlichen entfernten Proceduraufruf dem Programmierer weitestgehend transparent macht (bis auf die Fehlerbehandlung). Stub-Funktionen werden in der Regel automatisch erzeugt und serialisieren/deserialisieren Daten in ein zur Datenübertragung geeignetes Format (marshalling). Außerdem können Stub-Funktionen das notwendige Binden an einen passenden Server unterstützen. 233 234 KAPITEL 13. VERTEILTE ANWENDUNGEN Client Client Stub Server Stub Server Access Send Access Result Send Result Abbildung 13.2: RPC-Modell Bei RPC-Systemen gibt es unterschiedliche Semantiken, was insbesondere das Verhalten bei Kommunikationsfehlern angeht: • may-be: Es gibt keine Garantie, dass ein Aufruf auch wirklich ausgeführt wird. • at-least-once: Der Aufruf wird mindestens einmal ausgeführt. In Fehlersituationen kann es jedoch auch zur mehrfachen Ausführung kommen. Diese Semantik ist bei idempotenten Diensten unproblematisch. • at-most-once: Der Aufruf wird höchstens einmal ausgeführt. • exactly-once: Der Aufruf wird genau einmal ausgeführt. Erfordert in der Regel ein Transaktionsmodell bzw. fehlertolerante Kommunikationsprimitive. 13.2 N-Tier Architekturen Viele Anwendungen sind heutzutage als verteilte Anwendungen realisiert, bei denen nicht alle Komponenten auf einem Rechensystem ausgeführt werden. Oftmals besitzen moderne Anwendungen auch Web-basierten Schnittstellen. Ein typisches Beispiel ist ein Bestellsystem mit einer online Web-Schnittstelle. Verteilte Anwendungen wie z.B. ein Bestellsystem bestehen in vielen Fällen aus vier verschiedenen Elementen, die folgende Funktionen realisieren: • Die Präsentationsschnittstelle realisiert den Dialog mit dem Benutzer und erzeugt Zugriffe auf die Serverkomponenten. • Die Zugriffschnittstelle realisiert die Schnittstelle, über die Präsentationsschnittstellen auf die Anwendungslogik zugreifen. • Die Ablauflogik implementiert die eigentliche Logik der Anwendung. • Der Datenspeicher verwaltet die von Anwendung bearbeiteten Daten, oftmals realisiert in Form einer Datenbank. Die Komponenten lassen sich auf unterschiedliche Art und Weise auf die Computer in einem verteilten System verteilen. Für die verschiedenen Varianten hat sich der Überbegriff N-Tier Architektur etabliert, wobei N die Anzahl der Ebenen bezeichnet. 235 13.2. N-TIER ARCHITEKTUREN 13.2.1 2-Tier Architektur Bei der einfachsten Variante, die 2-Tier Architektur, verlagert man lediglich die Präsentationsschnittstelle auf einen anderen Rechner, typischerweise den lokalen Rechner des Benutzers. Beispiele für dieses Modell sind einfach Client/Server-Anwendungen im Internet (z.B. FTP, IMAP). Die gestrichelte Linie deutet die Grenze zwischen den einzelnen Ebenen an. presentation access logic data presentation presentation Abbildung 13.3: 2-Tier Architektur 13.2.2 3-Tier Architektur Bei Anwendungen, die komplexe Datenbestände verwalten müssen, empfiehlt sich die Verwaltung der Daten durch ein Datenbanksystem. Der Vorteil dieser Architektur liegt in der Trennung der Datenverwaltung von der eigentlichen Logik der Anwendung. presentation presentation legacy access logic data presentation Abbildung 13.4: 3-Tier Architektur Mit der 3-Tier Architektur lassen sich auch bestehende alte Anwendungen (legacy applications) oftmals leichter integrieren, indem man sie mehr oder weniger als eine Datenhaltungskomponente betrachtet. 13.2.3 4-Tier Architektur Bei der 4-Tier Architektur wird nun auch die Zugriffschnittstelle von der eigentlichen Anwendungslogik getrennt. Dadurch wird der Zugriff von der eigentlich Verarbeitung entkoppelt, was Vorteile bezüglich Skalierbarkeit und Sicherheit bietet. Typische Beispiele sind große Internet-Auftritte, bei denen die Präsentation durch einen Web-Browser erfolgt. Die Zugriffschnittstelle wird durch in der Regel mehrere Web-Server realisiert, wobei ein automatischer 236 KAPITEL 13. VERTEILTE ANWENDUNGEN Lastausgleich erfolgt. Die Anwendungslogik steckt in Software-Komponenten, die auf einem Anwendungsserver ausgeführt werden. Die bearbeiteten Daten befinden sich meist in relationalen bzw. objekt-orientierten Datenbanken. presentation legacy logic presentation access data logic presentation data Abbildung 13.5: 4-Tier Architektur Kapitel 14 CORBA Die Common Object Request Broker Architecture (CORBA) [20, 24] wird seit 1989 durch die Object Management Group (OMG) entwickelt und standardisiert. Hinter der OMG verbirgt sich ein Zusammenschluss von Herstellern, die durch die Schaffung einer gemeinsamen objekt-orientierten Kommunikations-Middleware die Interoperabilität von Produkten erreichen wollen. 14.1 CORBA Grundlagen Die Object Management Architecture (OMA) bildet die Grundlage von CORBA. Common Facilities Dist. Doc. Inf. Mgmt. Application Objects Sys. Mgmt. ... Object Request Broker ... Naming Events Trader Persistence Security Common Object Services (COS) Abbildung 14.1: Object Management Architecture (OMA) Sie setzt sich aus vier Komponenten zusammen: • Der Object Request Broker (ORB) bildet den Kern des Architekturmodells. Der ORB ermöglicht es einem Objekt, Anfragen an andere verteilte Objekte zu stellen bzw. Anfragen von anderen verteilten Objekten zu empfangen. Der ORB sorgt dabei für Transparenz bezüglich der tatsächlichen Implementation der Objekte und sichert damit die Interoperabilität. 237 238 KAPITEL 14. CORBA • Die Object Services stellen eine Menge von grundlegenden Diensten bereit, die zur Implementation und Benutzung von CORBA Objekten nützlich sind. Die in den Object Services bereitgestellten Dienste sind allgemein sinnvoll und unabhängig von Anwendungsgebieten. • Die Common Facilities sind eine Sammlung von Diensten, die für eine Menge von Applikationen in einem Problembereich nützlich sind. Im Gegensatz zu Object Services sind Common Facilities nicht so grundlegend und auf bestimmte Problembereiche zugeschnitten. • Die Application Objects sind die Objekte, die letztlich von Programmierern als Teil von Produkten oder aber im Rahmen von studentischen Arbeiten bzw. Praktika implementiert werden. Die Bedeutung dieser Application Objects wird (natürlich) von der OMG nicht definiert. 14.2 Object Request Broker (ORBs) Ein Object Request Broker (ORB) bildet die Infrastruktur, mit der Dienste von einem Objekt in Anspruch genommen werden können. Der Zugriff auf diese Objekte ist in allen Fällen transparent und unabhängig davon ob die Objekte über mehrere Rechner verteilt sind oder lokal in einem einzigen Prozess existieren. Der ORB übernimmt die Lokalisierung eines Objekts, die Übertragung und Anpassung eines Aufrufs an das Zielobjekt sowie die Übertragung der Ergebnisse oder Ausnahmen an den Aufrufer. 14.3 Object Services Die Object Services stellen Hilfsdienste bereit, ohne die verteilte Applikationen nicht entwickelt werden können. Die Object Services gehören zur Grundfunktionalität einer CORBA Implementation und müssen in allen CORBA-konformen Systemen vorhanden sein. Die im folgenden kurz beschriebenen CORBA Services sind lediglich eine kleine Auswahl der standardisierten Object Services: • Der Naming Service ist ein Dienst, mit dem Objekten Namen zugeordnet werden können. Jeder Name existiert in einem Kontext, der selbst wiederum einen Namen haben kann. Der Namensraum ist also hierarchisch strukturiert. • Der Event Service erlaubt es, Nachrichten über eingetretene Ereignisse asynchron einem oder mehreren Empfängern zuzustellen. Entsprechend wird zwischen Erzeugern und Verbrauchern von Nachrichten unterschieden. Die Nachrichten selbst werden durch so genannte Kanäle zugestellt, wobei ein Nachrichten-Verbraucher nicht notwendigerweise aktiv sein muss, während eine Nachricht in einem Kanal eingebracht wird. • Der Life Cycle Service stellt Dienste bereit, mit denen der Lebenslauf eines Objekts kontrolliert werden kann. Der Life Cycle Service umfasst insbesondere Dienste zum Erzeugen, Löschen, Kopieren oder Verschieben von Objekten. 14.4 Common Facilities Neben den Object Services, die grundlegende Basisfunktionen bereitstellen, gibt es weitere standardisierte Dienste, die in speziellen Anwendungsbereichen sinnvoll sind. 14.5 Komponenten eines ORBs Das erklärte Ziel ist die möglichst transparente Kommunikation zwischen verteilten Objekten, die in verschiedenen Sprachen implementiert sein können und auf verschiedenen Betriebssystemen ablaufen. Die Un- 239 14.6. INTERFACE DEFINITION LANGUAGE (IDL) abhängigkeit von den verwendeten Programmiersprachen wird durch eine Beschreibung der Objekt-Schnittstellen in einer neutralen Sprachen, der CORBA Interface Definition Language, erreicht. Neben dieser IDL gibt es weitere CORBA-Komponenten. Client Interface Object Implementation Dynamic IDL ORB Invocation Stubs Interface Static IDL Dynamic Skeleton Skeleton Object Adapter Repository Implementation Repository ORB Core Abbildung 14.2: Komponenten eines Object Request Brokers (ORB) Bemerkungen: • Das Dynamic Invocation Interface erlaubt die Konstruktion von Aufrufen zur Laufzeit. • Die IDL Stubs werden mit Hilfe eines Compilers aus einer IDL-Definition erzeugt und realisieren statische Stub-Prozeduren auf der Client-Seite. • Das ORB Interface stellt generelle Infrastruktur zur Verfügung. • Die Static IDL Skeleton werden ebenfalls mit Hilfe eines Compilers aus einer IDL-Definition erzeugt und realisieren statische Stub-Prozeduren auf der Server-Seite. • Das Dynamic Skeleton Interface erlaubt die Konstruktion von Objektimplementationen zur Laufzeit. • Der Objektadapter übernimmt das Erzeugen, Starten, Beenden und Löschen von Objektimplementationen. • Informationen über die bekannten IDL-Schnittstellen werden im Interface Repository verwaltet. • Informationen über die bekannten Implementationen werden im Implementation Repository verwaltet. 14.6 Interface Definition Language (IDL) Die Interface Definition Language (IDL) ist eine formale Sprache, mit der die von außen sichbare Schnittstelle eines Objekts definiert wird. Eine IDL-Schnittstelle muss alle Informationen enthalten, die zur Benutzung des durch ein Objekt realisierten Dienstes notwendig ist. Die IDL ist eine abstrakte deklarative Sprache, die keine konkrete Datendarstellung impliziert oder eine bestimmte Implementation eines Objekts festlegt. IDL-Definitionen werden mit Hilfe von speziellen Übersetzern auf übliche Programmiersprachen abgebildet. So existieren beispielsweise standardisierte Sprachabbildungen für C, C++, Smalltalk oder Java. (Dabei ist zu beachten, dass für einige Sprachen Einschränkungen existieren. Beispielsweise verfügt Java über keine vorzeichenlosen Zahlentypen, die aber in der IDL existieren. Die Spezifikation überlässt es in solchen Fällen dem Programmierer, die sich daraus ergebenden Probleme korrekt zu behandeln.) IDL-Definitionen befinden sich in so genannten Modulen. Ein Modul definiert einen Namensraum. Alle Bezeichner in einem Modul müssen eindeutig sein. Ein Modul enthält neben Definitionen von Datentypen, Konstanten und Exceptions im wesentlichen Definitionen von so genannten Interfaces. Ein Interface definiert eine CORBA Objektklasse, die durch die Menge der Attribute und Operationen beschrieben wird. Ein 240 KAPITEL 14. CORBA Interface kann von einem anderen Interface abgeleitet werden, wobei die Attribute und Operationen an die abgeleitete Klasse vererbt werden. Achtung: Bezeichner, die sich nur in der Groß-/Kleinschreibung unterscheiden, sind in der IDL identisch. Die Definitionen typedef long Foo; const Foo foo = 1; kollidieren daher im Namensraum und stellen einen Fehler dar. 14.6.1 Typsystem object reference basic types constructed types short long long long unsigned short unsigned long unsigned long long float double long double fixed char wchar string wstring boolean octet enum any struct sequence union array Abbildung 14.3: Datentypen der Interface Definition Language (IDL) Bemerkungen: • Die Datentypen short, long und long long sind ganze vorzeichenbehaftete Zahlen (16, 32 bzw. 64 Bit). • Die Datentypen unsigned short, unsigned long und unsigned long long sind ganze vorzeichenlose Zahlen (16, 32 bzw. 64 Bit). • Die Datentypen float, double und long double entsprechen den IEEE 754-1985 einfachen, doppelten und den erweiterten doppelten Gleitpunktzahlen. • Der Datentyp char ist ein 8-Bit Zeichen während der Datentyp wchar ein Zeichen in mehreren Bytes halten kann. • Analog ist der Datentyp string eine Zeichenkette bestehend aus 8-Bit Zeichen während eine Variable vom Datentyp wstring Zeichen enthalten kann, die aus mehreren Bytes bestehen. 14.6. INTERFACE DEFINITION LANGUAGE (IDL) 241 • Der Datentyp enum definiert eine Aufzählung. • Der Datentyp octet repräsentiert ein einzelnes Byte, das während einer Übertragung nicht verändert wird. • Der Datentyp any kann den Wert eines beliebigen anderen Datentyps enthalten, wobei die Wert mit einem so genannten TypeCode assoziiert ist. • Datentypen können zu einer Struktur struct zusammengefasst werden. Die Mitglieder der Struktur (member) können selbst wieder zusammengesetzte Datentypen sein. • Der Datentyp union stellt eine Vereinigung dar, wobei das Unterscheidungsmerkmal benannt werden muss (discriminated union). • Eine sequence ist eine Sequenz von einem Datentyp und entspricht einem Feld oder Array. 14.6.2 Konstanten Konstanten können mit Hilfe von Ausdrücken definiert werden, die sehr ähnlich zu Ausdrücken in Sprachen wie Java oder C/C++ sind. Konstanten sind nur für die Basis-Datentypen vorgesehen. <const_dcl> ::= "const" <const_type> <identifier> "=" <const_exp> 14.6.3 Ausnahmen Die Definition von Ausnahmen (exceptions) ähnelt der Definition von Strukturen. Die Mitglieder (member) einer Exception beschreiben eine Ausnahmesituation genauer. <except_dcl> ::= "exception" <identifier> "{" <member>* "}" 14.6.4 Interfaces Ein Interface definiert die Schnittstelle für ein CORBA-Objekt und legt die Signaturen der Operationen und die Attribute des Objekts fest. <interface_dcl> ::= <interface_header> "{" <interface_body> "}" <interface_header> ::= [ "abstract" | "local" ] "interface" <identifier> [ <interface_inheritance_spec> ] <interface_inheritance_spec> ::= ":" <interface_name> { "," <interface_name> }* <interface_name> ::= <scoped_name> <interface_body> <export> ::= <export>* ::= <type_dcl> ";" | <const_dcl> ";" | <except_dcl> ";" | <attr_dcl> ";" | <op_dcl> ";" Bemerkungen: • In einem Interface können auch Datentypen, Konstante und Ausnahmen definiert werden, die dann nur in dem Sichtbarkeitsbereich (scope) des Interfaces gültig sind. • Ein Interface kann von mehreren anderen Interfaces abgeleitet werden (Mehrfachvererbung). 242 KAPITEL 14. CORBA Operationen In der CORBA-IDL werden lediglich die Signaturen von Operationen beschrieben. Es gibt keine Mechanismen in der IDL, um die Semantik einer Operation zu programmieren“. ” <op_dcl> ::= [<op_attribute>] <op_type_spec> <identifier> <parameter_dcls> [<raises_expr>] [<context_expr>] <op_attribute> ::= "oneway" <op_type_spec> ::= <param_type_spec> | "void" <parameter_decls> <param_decl> <param_attribute> <param_type_spec> ::= ::= ::= ::= "(" <param_decl> { "," <param_decl> }* ")" <param_attribute> <param_type_spec> <simple_declarator> "in" | "out" | "inout" <base_type_spec> <raises_expr> ::= "raises" "(" <scoped_name> { "," <scoped_name> }* ")" <context_expr> ::= "context" "(" <string_literal> { "," <string_literal> }* ")" Bemerkungen: • Die Parameter-Attribute zeigen ob ein Parameter vom Client zum Server übertragen wird (in), ein Parameter vom Server zum Client übertragen wird (out), oder ein Parameter in beide Richtungen übertragen wird (inout). • Die Exceptions, die beim Aufruf einer Operation auftreten können, werden durch die optionale raisesKlausel angegeben. • Kontextinformation des Client können der Objektimplementation zur Verfügung gestellt, um z.B. das Leistungsverhalten einer Objektimplementation zu beeinflussen. Attribute Ein Interface kann Attribute haben. Die Definition eines Attributs ist äquivalent zur Definition von zwei Zugriffsfunktionen zum Lesen und Schreiben. <attr_dcl> ::= ["readonly"] "attribute" <param_type_spec> <simple_declarator> { "," <simple_declarator> }* 14.6.5 Module Ein Modul erzeugt einen Namensraum für eine Menge von Definitionen. Module können ineinander geschachtelt werden, wodurch ein hierarchischer Namensraum entsteht. <module> ::= "module" <identifier> "{" <definition>+ "}" 14.7 Objekt-Referenzen Damit ein Client ein CORBA-Objekt aufrufen kann, muss der Client zunächst eine Referenz auf das Objekt (object reference) besitzen. Das genaue Format der Objekt-Referenzen ist nicht festgelegt und zwei verschiedene Implementationen können unterschiedliche Formate benutzen. Eine ausgegebene Objekt-Referenz ist auch nur für die Lebenszeit des Clients gültig, der sie bekommen hat. Es gibt eine spezielle ausgezeichnete Objekt-Referenz, die kein Objekt referenziert. 243 14.8. JAVA LANGUAGE MAPPING IDL Datentyp boolean char wchar octet string wstring short unsigned short long unsigned long long long unsigned long long float double fixed Java Datentyp boolean char char byte java.lang.String java.lang.String short short int int long long float double java.math.BigDecimal Tabelle 14.1: Abbildung der Basis-Datentypen • Eine stringified object reference ist eine ASCII-Darstellung einer Objekt-Referenz. 14.8 Java Language Mapping Die Abbildung der CORBA-IDL auf die Implementationssprache Java ist in [19] definiert. Die Abbildung ist in vielen Teilen relativ einfach, da Java der IDL sehr nahe kommt. Allerdings gibt es in einigen Details auch durchaus Überraschungen. • IDL-Module werden auf Java-Packages abgebildet. • Die Basis-Datentypen werden entsprechend Tabelle 14.1 auf Java-Datentypen abgebildet. Achtung: Bei vorzeichenlosen Zahlen können plötzlich negative Werte in Java auftreten. • Strukturen und Vereinigungen werden auf Java Klassen abgebildet. • Eine Sequenz wird auf ein Array in Java abgebildet. • Aufzählungen (enum) werden auf Java Klassen abgebildet, die entsprechende Konstanten definieren und Methoden zur Umwandlung bereitstellen. • Für jedes Interface werden zwei Java Interfaces erzeugt. Das erste Interface modelliert die Operationen und das zweite davon abgeleitete Interface den Rest. • IDL-Exceptions werden auf Java Klassen abgebildet, die indirekt von java.lang.Exception abgeleitet sind. • Für definierte Datentypen werden so genannte Holder Classes erzeugt, die zur Realisierung von out und inout Parametern benötigt werden. • Zur Einschränkung von Objekt-Referenzen auf konkrete Java Objekte werden so genannte Helper Classes erzeugt. Das folgende Beispiel definiert ein IDL-Interface Hello mit der Operation sayHello() in dem Modul HelloModule. 244 KAPITEL 14. CORBA // hello/Hello.idl module HelloModule { interface Hello { string sayHello(); }; }; Die Übersetzung des IDL Moduls nach Java liefert die in Abbildung 14.4 dargestellten Klassen. «interface» HelloOperations «interface» Object HelloHelper «interface» Hello «interface» IDLEntity _HelloStub ObjectImpl _HelloImplBase Abbildung 14.4: Klassendiagramm für die von Hello.idl erzeugten Java Klassen Der Quellcode für eine Implementation des Hello-Interfaces: // hello/HelloServant.java import HelloModule._HelloImplBase; class HelloServant extends _HelloImplBase { public String sayHello() { return "Hello World"; } } Der Quellcode für einen einfachen Client: // hello/HelloClient.java import import import import import import HelloModule.Hello; HelloModule.HelloHelper; org.omg.CORBA.ORB; org.omg.CosNaming.NameComponent; org.omg.CosNaming.NamingContext; org.omg.CosNaming.NamingContextHelper; 14.8. JAVA LANGUAGE MAPPING public class HelloClient { public static void main(String args[]) { try { org.omg.CORBA.Object obj; NamingContext ncxt; // Initialize the ORB and retrieve and narrow the // reference to the CORBA naming service object. ORB orb = ORB.init(args, null); obj = orb.resolve_initial_references("NameService"); ncxt = NamingContextHelper.narrow(obj); // Lookup the server object in the naming service and // narrow it down. NameComponent nc = new NameComponent("Hello", ""); NameComponent path[] = { nc }; Hello helloRef = HelloHelper.narrow(ncxt.resolve(path)); // Now we are ready to invoke methods on the potentially // remote server object. String hello = helloRef.sayHello(); System.out.println(hello); } catch (Exception e) { System.err.println("HelloClient: " + e.toString()); System.exit(1); } } } Der Quellcode für einen einfachen Server: // hello/HelloServer.java import import import import import HelloServant; org.omg.CORBA.ORB; org.omg.CosNaming.NameComponent; org.omg.CosNaming.NamingContext; org.omg.CosNaming.NamingContextHelper; public class HelloServer { public static void main(String args[]) throws java.lang.InterruptedException { try { org.omg.CORBA.Object obj; NamingContext ncxt; // Initialize the ORB and retrieve and narrow the 245 246 KAPITEL 14. CORBA // reference to the CORBA naming service object. ORB orb = ORB.init(args, null); obj = orb.resolve_initial_references("NameService"); ncxt = NamingContextHelper.narrow(obj); // Create the server object and register it in the ORB. HelloServant hello = new HelloServant(); orb.connect(hello); // Register the server object in the naming service. NameComponent nc = new NameComponent("Hello", ""); NameComponent path[] = { nc }; ncxt.rebind(path, hello); // Wait for something not to happen to keep the ORB alive. java.lang.Object sync = new java.lang.Object(); synchronized(sync){ sync.wait(); } } catch (Exception e) { System.err.println("HelloServer: " + e.toString()); System.exit(1); } } } 14.9 Naming Service Der CORBA Naming Service [] ist ein grundlegender Common Object Service zur Zuordnung und Verwaltung von Namen von Objekten. Der Namensraum ist hierarchisch strukturiert und besteht aus internen Knoten (naming contexts) und Blättern (name bindings). • Die Zuordnung eines Namens zu einem CORBA-Objekt wird durch ein Binding dargestellt. • Ein NamingContext Objekt verwaltet Bindings und andere NameContexts. • Der Name eines NamingContexts bzw. eines Bindings wird als NameComponent bezeichnet und besteht aus zwei Zeichenfolgen: einem String zur Identifikation und einem String, der die Art des Bindings näher beschreibt. • Eine Sequenz von NameComponents beschreibt einen Pfad im Namensraum. • Mit Hilfe eines BindingIterators kann man über die Elemente in einem NamingContext iterieren. Achtung: Der Iterator existiert auf dem Name Server und sollte tunlichst nach Gebrauch gelöscht werden. Name Server dürfen auch selbständig Iteratoren löschen, woraus ein Client sinnvoll reagieren sollte. • Pfade können als stringified names dargestellt werden, wobei NameComponents durch ’/’ voneinander getrennt werden und die Identifikation und die Art eines Bindings durch einen Punkt getrennt werden (z.B. maumau/player1.mau). Der CORBA Naming Service ist in folgendem IDL-Modul spezifiziert: 14.9. NAMING SERVICE 247 //File: CosNaming.idl //The only module of the Naming Service #ifndef _COS_NAMING_IDL_ #define _COS_NAMING_IDL_ #pragma prefix "omg.org" module CosNaming { typedef string Istring; struct NameComponent { Istring id; Istring kind; }; typedef sequence <NameComponent> Name; enum BindingType { nobject, ncontext }; struct Binding { Name binding_name; BindingType binding_type; }; // Note: In struct Binding, binding_name is incorrectly defined // as a Name instead of a NameComponent. This definition is // unchanged for compatibility reasons. typedef sequence <Binding> BindingList; interface BindingIterator; interface NamingContext { enum NotFoundReason { missing_node, not_context, not_object }; exception NotFound { NotFoundReason why; Name rest_of_name; }; exception CannotProceed { NamingContext cxt; Name rest_of_name; }; exception InvalidName{}; exception AlreadyBound{}; exception NotEmpty{}; void bind(in Name n, in Object obj) raises(NotFound, CannotProceed, InvalidName, AlreadyBound); void rebind(in Name n, in Object obj) 248 KAPITEL 14. CORBA raises(NotFound, CannotProceed, InvalidName); void bind_context(in Name n, in NamingContext nc) raises(NotFound, CannotProceed, InvalidName, AlreadyBound); void rebind_context(in Name n, in NamingContext nc) raises(NotFound, CannotProceed, InvalidName); Object resolve(in Name n) raises(NotFound, CannotProceed, InvalidName); void unbind(in Name n) raises(NotFound, CannotProceed, InvalidName); NamingContext new_context(); NamingContext bind_new_context(in Name n) raises(NotFound, AlreadyBound, CannotProceed, InvalidName); void destroy() raises(NotEmpty); void list(in unsigned long how_many, out BindingList bl, out BindingIterator bi); }; interface BindingIterator { boolean next_one(out Binding b); boolean next_n(in unsigned long how_many, out BindingList bl); void destroy(); }; }; #endif /* ifndef _COS_NAMING_IDL_ */ 14.10 Mau Mau In diesem Abschnitt soll ein verteiltes Mau-Mau-Spiel realisiert werden. Die IDL-Schnittstelle sieht wie folgt aus: /* * maumau/MauMau.idl * * This file defines the CORBA IDL interface for playing the famous * MauMau card game. Everyone is invited to write his/her own player. * * (c) 1998 TU Braunschweig (Juergen Schoenwaelder, Ralph Wittmann) * (c) 2001 Uni Osnabrueck (Juergen Schoenwaelder) */ 249 14.10. MAU MAU module MauMau { enum Color { square, heart, spade, club }; enum Value { seven, eight, nine, ten, jack, queen, king, ace }; struct Card { Color color; Value value; }; const unsigned short number_colors = 4; const unsigned short number_values = 8; const unsigned short number_cards = 32; /* * The following definitions define the interface for a CORBA MauMau * player object. The Player interface defines the following operations: * * - The start() operation is called to initialize the player object * for a new game. * * - The take() operation is called to send cards to a player object * (this can happen when the game starts or during the game). * * - The play() operation is called whenever the player has to play * a card. The result of the play() operation is either the value * ‘serve’ (this is the normal case) or ‘skip’ (the player could * not play a card). * * - The info() operation is called after each turn in order to inform * all players about what has happened. * * - The bye() operation is called to end a game. */ enum Status { serve, skip }; interface Player { void start(in unsigned short players, in unsigned short ident); // # players in the game // # assigned to this player void take(in Card card); // card you have to take Status play(in Card stack, in unsigned short age, out Card card, inout Color color); // // // // card on the stack age of card on the stack card played (if any) new color (if any) void info(in in in in in // // // // // identity of the player card on the stack age of card on the stack new color (if any) # cards taken or 0 unsigned short player, Card stack, unsigned short age, Color color, unsigned short taken); 250 KAPITEL 14. CORBA oneway void bye(in string s); // reason phrase }; }; Die Implementation des Spielverwalters und der Spieler erfolgt nach folgenden Klassendiagramm: «interface» PlayerOperations +start() +take() +play(): Status +info() +bye() «interface» _PlayerImplBase Player Status PlayerServer 1 n PlayerServant -pile: Vector Server Implementation 1 Color 1 Value Card +color: Color n +value: Value n Game +players: Player[] +deck: OwnedCard[] +Game(NamingContext) +start() +end() +run() OwnedCard -owner: short -card: Card Client Implementation Abbildung 14.5: Klassendiagramm für das Mau-Mau-Spiel Die Interaktionen zwischen Spielern und dem Spielverwalter, die durch die IDL-Schnittstelle festgelegt sind, sind in folgendem Sequenzdiagramm dargestellt: 251 14.10. MAU MAU aGame start() aPlayer1 aPlayer2 aPlayer3 start() start() start() * take(Card) * take(Card) * take(Card) run() play() info() info() info() play() end() bye() bye() bye() Abbildung 14.6: Sequenzdiagramm für das Mau-Mau-Spiel Die Implementation der Klasse PlayerServant: // maumau/PlayerServant.java import import import import import import import import import MauMau._PlayerImplBase; MauMau.Card; MauMau.CardHolder; MauMau.Color; MauMau.ColorHolder; MauMau.Value; MauMau.Status; java.util.Vector; java.util.Enumeration; class PlayerServant extends _PlayerImplBase { 252 KAPITEL 14. CORBA private short ident; private short players; private Vector pile; public PlayerServant() { ident = 0; players = 0; pile = null; } /** * A new game is about to start. Initialize the pile of * cards. */ public void start(short players, short ident) { this.players = players; this.ident = ident; pile = new java.util.Vector(); } /** * Take a card. */ public void take(Card card) { pile.addElement(card); } /** * Play a card making sure we honor the rules of the game. * Everything else is stupid - playing the first suitable card we * find without any sophisticated strategies. */ public Status play(Card stack, short age, CardHolder ch, ColorHolder wh) { Card c; if (stack.value == Value.seven && age == 0) { c = find(Value.seven); if (c != null) { return serve(c, ch, wh); } return skip(stack, ch, wh); } if (stack.value == Value.eight && age == 0) { c = find(Value.eight); if (c != null) { 14.10. MAU MAU 253 return serve(c, ch, wh); } return skip(stack, ch, wh); } if (stack.value == Value.jack) { for (Enumeration e = pile.elements(); e.hasMoreElements();) { c = (Card) e.nextElement(); if ((c.color == wh.value && c.value != Value.jack) || (c.value == Value.jack && age > 0)) { return serve(c, ch, wh); } } return skip(stack, ch, wh); } for (Enumeration e = pile.elements(); e.hasMoreElements();) { c = (Card) e.nextElement(); if (c.value == Value.jack) continue; if (c.value == stack.value || c.color == stack.color) { return serve(c, ch, wh); } } c = find(Value.jack); if (c != null) { return serve(c, ch, wh); } return skip(stack, ch, wh); } /** * Information how the game proceeds. We choose to ignore it. */ public void info(short player, Card stack, short age, Color color, short taken) { // This is a dumb player who ignores info about the game. } /** * The game is over. Drop the pile of cards. */ public void bye(String s) { pile = null; } /** * Helper method to skip. Note that we still have to initialize * the holder classes. 254 KAPITEL 14. CORBA */ private Status skip(Card c, CardHolder ch, ColorHolder wh) { ch.value = c; wh.value = c.color; return Status.skip; } /** * Helper method to serve a card by removing it from the pile * of cards we have and updating the holder classes. */ private Status serve(Card c, CardHolder ch, ColorHolder wh) { pile.removeElement(c); ch.value = c; wh.value = c.color; return Status.serve; } /** * Helper method to find a card in our pile with a given value. */ private Card find(Value value) { for (Enumeration e = pile.elements(); e.hasMoreElements();) { Card c = (Card) e.nextElement(); if (c.value == value) { return c; } } return null; } } Die Implementation der Klasse PlayerServer: // maumau/PlayerServer.java import import import import import import import import import org.omg.CORBA.ORB; org.omg.CORBA.UserException; org.omg.CosNaming.NameComponent; org.omg.CosNaming.NamingContext; org.omg.CosNaming.NamingContextHelper; org.omg.CosNaming.NamingContextPackage.NotFound; org.omg.CosNaming.NamingContextPackage.CannotProceed; org.omg.CosNaming.NamingContextPackage.InvalidName; org.omg.CosNaming.NamingContextPackage.AlreadyBound; public class PlayerServer { 14.10. MAU MAU 255 static final int numPlayers = 3; static final String context = "maumau"; static NamingContext addContext(NamingContext ncxt, String id, String kind) throws NotFound, CannotProceed, InvalidName, AlreadyBound { NameComponent nc = new NameComponent(id, kind); NameComponent path[] = { nc }; return ncxt.bind_new_context(path); } static void addBinding(NamingContext ncxt, String id, String kind, org.omg.CORBA.Object obj) throws NotFound, CannotProceed, InvalidName { NameComponent nc = new NameComponent(id, kind); NameComponent path[] = { nc }; ncxt.rebind(path, obj); } public static void main(String args[]) throws java.lang.InterruptedException { try { org.omg.CORBA.Object obj; NamingContext ncxt; ORB orb = ORB.init(args, null); obj = orb.resolve_initial_references("NameService"); ncxt = NamingContextHelper.narrow(obj); PlayerServant players[] = new PlayerServant[numPlayers]; try { ncxt = addContext(ncxt, context, ""); } catch (Exception AlreadyBound) {}; for (int i = 0; i < numPlayers; i++) { players[i] = new PlayerServant(); orb.connect(players[i]); addBinding(ncxt, "simple player " + i, "", players[i]); } java.lang.Object sync = new java.lang.Object(); synchronized(sync){ sync.wait(); } } catch (UserException e) { e.printStackTrace(); System.exit(1); } } } Die Implementation der Klasse Game: 256 KAPITEL 14. CORBA // maumau/Game.java import import import import import import import import import import import import import import import import import import import MauMau.Player; MauMau.PlayerHelper; MauMau.Card; MauMau.CardHolder; MauMau.Color; MauMau.ColorHolder; MauMau.Value; MauMau.Status; OwnedCard; org.omg.CORBA.ORB; org.omg.CORBA.UserException; org.omg.CosNaming.NameComponent; org.omg.CosNaming.NamingContext; org.omg.CosNaming.NamingContextHelper; org.omg.CosNaming.BindingHolder; org.omg.CosNaming.BindingListHolder; org.omg.CosNaming.BindingIteratorHolder; java.util.Vector; java.util.Enumeration; public class Game { static final String context = "maumau"; /** * Exception which is thrown if a player tries to play * an illegal card. */ public class IllegalCardException extends Exception { public IllegalCardException() { super(); } public IllegalCardException(String s) { super(s); } } private private private private private private OwnedCard deck[]; Card stack; Color color; short age; boolean verbose; Player players[]; // // // // // // Owned cards in the game. Card currently on the stack. Color requested. Age of the card on the deck. Generate verbose output if true. Players in a game. private static final Color colors[] = { Color.square, Color.heart, Color.spade, Color.club }; private static final Value values[] = { Value.seven, Value.eight, 257 14.10. MAU MAU Value.nine, Value.ten, Value.jack, Value.queen, Value.king, Value.ace }; static final short TALON = -1; static final short PILE = -2; // card is in the talon // card is on the stack static final short INITIAL = 3; // initial number of cards per player /** * Initialize the game by obtaining the players from the naming * service and initializing the deck of cards. */ public Game(NamingContext ncxt) { initPlayers(ncxt); initDeck(); verbose = true; } /** * Initialize the deck by creating a new set of cards and * assigning them to the talon. Pick a random card and put it on * the stack. */ private void initDeck() { short l; short i = 0; deck = new OwnedCard[colors.length * values.length]; for (int c = 0; c < colors.length; c++) { for (int v = 0; v < values.length; v++) { Card card = new Card(colors[c], values[v]); deck[i++] = new OwnedCard(card, TALON); } } l = next(); stack = deck[l].card; deck[l].owner = PILE; } /** * Initialize the players by iterating through the naming context * and resolving the names to players. */ private void initPlayers(NamingContext ncxt) { short i; java.util.Vector v = new java.util.Vector(); boolean ok; 258 KAPITEL 14. CORBA BindingHolder bh = new BindingHolder(); BindingListHolder blh = new BindingListHolder(); BindingIteratorHolder bih = new BindingIteratorHolder(); ncxt.list(0, blh, bih); for (ok = bih.value.next_one(bh); ok; ok = bih.value.next_one(bh)) { try { org.omg.CORBA.Object obj = ncxt.resolve(bh.value.binding_name); Player p = PlayerHelper.narrow(obj); v.add(p); } catch (UserException e) { e.printStackTrace(); System.exit(1); } } bih.value.destroy(); players = new Player[v.size()]; v.toArray(players); } /** * Count the number of cards owned by a given player. */ private int count(short player) { int cnt = 0; for (int i = 0; i < deck.length; i++) { if (deck[i].owner == player) { cnt++; } } return cnt; } /** * Move all cards in the pile except the card on top of the stack * back to the talon. Note: We do not really shuffle the cards * here - instead we pick a random card when we need one. */ public void shuffle(Card stack) { for (int i = 0; i < deck.length; i++) { if (deck[i].owner == PILE && deck[i].card != stack) { deck[i].owner = TALON; } } } 14.10. MAU MAU /** * Pick the next random card from the talon. Simply pick random * cards unitl we have found one which is in the talon. */ public short next() { if (count(TALON) == 0) { shuffle(stack); if (count(TALON) == 0) { return -1; // throw an exception here? } } while (true) { short k = (short) (Math.random() * (colors.length * values.length)); if (deck[k].owner == TALON) { return k; } } } /** * Show which card is owned by whom. */ public void show(short player) { if (verbose) { System.out.print(player + ":"); for (int i = 0; i < deck.length; i++) { if (deck[i].owner == player) { System.out.print(" " + deck[i].card); } } System.out.println(); } } /** * Give a number of cards to a player. */ public short give(short player, short count) { for (short l = 0; l < count; l++) { short k = next(); if (k < 0 || k >= deck.length) { return l; } deck[k].owner = player; players[player].take(deck[k].card); if (verbose) { 259 260 KAPITEL 14. CORBA System.out.println(player + ": receives card " + deck[k].card); } } return count; } /** * Start the game by calling the start operation for each player * and dispensing the initial number of cards to each player. */ public void start() { for (short i = 0; i < players.length; i++) { players[i].start((short) players.length, i); } for (short i = 0; i < players.length; i++) { give(i, INITIAL); } } /** * End the game by calling the bye operation for each player. */ public void end(String s) { if (verbose) { System.out.println(s); } for (short i = 0; i < players.length; i++) { players[i].bye(s); } } /** * Send about information to all players. */ public void info(short player, short taken) { for (short i = 0; i < players.length; i++) { players[i].info(player, stack, age, color, taken); } } /** * Check whether a given response is valid or whether someone is * cheating by playing e.g. with stolen cards. */ private void check(short player, Card card) throws IllegalCardException { 14.10. MAU MAU 261 if (stack.value == Value.jack) { if (age == 0) { if (card.value == Value.jack) { throw new IllegalCardException( player + ": not allowed to put a jack on a jack"); } } else { if (card.value != Value.jack && card.color != color) { throw new IllegalCardException( player + ": must serve with the requested color"); } } } else { if (card.color != stack.color && card.value != stack.value && card.value != Value.jack) { throw new IllegalCardException( player + ": must serve color or value"); } if ((stack.value == Value.seven || stack.value == Value.eight) && age == 0 && card.value != stack.value) { throw new IllegalCardException( player + ": must serve the value"); } } for (int k = 0; k < deck.length; k++) { Card c = deck[k].card; if (c.value == card.value && c.color == card.color) { if (deck[k].owner != player) { throw new IllegalCardException( player + ": must play with his own cards"); } deck[k].owner = PILE; stack = card; return; } } throw new IllegalCardException(player + ": must play known card"); } /** * Run the game by asking each player in turn to play a card * until one player has no cards left. */ public short run() throws IllegalCardException { short i; short penalty = 0; short taken = 0; age = 0; 262 KAPITEL 14. CORBA color = stack.color; for (i = 0; true; i = (short) ((i+1) % players.length)) { if (verbose) { System.out.println(); System.out.print("S: " + stack); if (stack.value == Value.jack) { System.out.print(" (" + color + ")"); } System.out.println(); show(i); } CardHolder ch = new CardHolder(); ColorHolder wh = new ColorHolder(color); Status s = players[i].play(stack, age, ch, wh); if (s == Status.skip) { if (stack.value == Value.eight && age == 0) { if (verbose) { System.out.println(i + ": must pause"); } } else if (stack.value == Value.seven && age == 0) { if (verbose) { System.out.println(i + ": must take " + penalty + " cards"); } taken = give(i, penalty); } else { if (verbose) { System.out.println(i + ": skips and must take 1 card"); } taken = give(i, (short) 1); } penalty = 0; age++; } if (s == Status.serve) { if (ch.value.value == Value.jack) { check(i, ch.value); color = wh.value; age = 0; if (verbose) { System.out.println(i + ": serves with " + + " and wishes color " } } else { if (ch.value.value == Value.seven) penalty += check(i, ch.value); age = 0; if (verbose) { System.out.println(i + ": serves with " + stack + color); 2; stack); 263 14.10. MAU MAU } } } info(i, taken); if (count(i) == 0) { return i; } } } /** * Connect to the name service and run the game. */ public static void main(String args[]) { NamingContext ncxt = null; Game game = null; short winner; try { org.omg.CORBA.Object obj; ORB orb = ORB.init(args, null); obj = orb.resolve_initial_references("NameService"); ncxt = NamingContextHelper.narrow(obj); NameComponent nc = new NameComponent(context, ""); NameComponent path[] = {nc}; obj = ncxt.resolve(path); ncxt = NamingContextHelper.narrow(obj); } catch (UserException e) { e.printStackTrace(); System.exit(1); } try { game = new Game(ncxt); game.start(); winner = game.run(); game.end("player " + winner + " wins the game"); } catch (IllegalCardException e) { game.end(e.getMessage()); } } } Die Implementation der Hilfsklasse OwnedCard: // maumau/OwnedCard.java import MauMau.Card; 264 KAPITEL 14. CORBA import MauMau.Color; import MauMau.Value; public class OwnedCard { public Card card; public short owner; public OwnedCard(Card card, short owner) { this.card = card; this.owner = owner; } } 14.11 Schlußbemerkungen Aus der JDK Dokumentation: NOTE: Although it is true in theory that ORBs written in different languages should be able to talk to each other, we haven’t tested the interoperability of the Java ORB with other vendor’s ORBs. Kapitel 15 Remote Method Invocation (RMI) Besteht eine komplexe verteilte Applikation komplett aus Java-Komponenten, so ist die Verwendung von CORBA als Kommunikations-Middleware unter Umständen zu aufwendig. Als Alternative bietet sich in diesem Fall die Java Remote Method Invocation (RMI) an, bei der Schnittstellen von entfernten Objekten einfach durch Java Interfaces beschrieben werden. Ein Java Interface wird als Remote Interface bezeichnet, wenn es die folgenden Anforderungen erfüllt: 1. Das Interface muss public deklariert sein. 2. Das Interface muss von der Klasse java.rmi.Remote abgeleitet sein. 3. Da ein Methodenaufruf an ein entferntes Objekt aus verschiedenen Gründen fehlschlagen kann, muss für jede Methode die Ausnahme RemoteException (oder eine Oberklasse von RemoteException) deklariert werden. 4. Alle Datenobjekte, die als Argumente übergeben oder als Resultat einer RMI-Methode benutzt werden, müssen als Remote Interface deklariert sein. Ein einfaches Beispiel, das den Anforderungen an ein Remote Interface genügt, ist das folgende Interface Hello: // hello/Hello.java import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { String sayHello() throws RemoteException; } Bei der Implementation eines Remote Interface ist festzulegen, welcher Kommunikationsmechanismus benutzt werden soll und ob das Server-Objekt ständig verfügbar sein soll oder bei Bedarf aktiviert werden soll. Im einfachsten Fall, wenn das Server-Objekt ständig verfügbar ist und die Kommunikation über das Transmission Control Protocol (TCP) abgewickelt wird, kann man die Server-Klasse von der Hilfsklasse UnicastRemoteObject ableiten. // hello/HelloServant.java 265 266 KAPITEL 15. REMOTE METHOD INVOCATION (RMI) import Hello; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloServant extends UnicastRemoteObject implements Hello { public HelloServant() throws RemoteException { super(); } public String sayHello() { return "Hello World"; } } Bemerkungen: • Der Konstruktor muss ebenfalls eine RemoteException deklarieren, da das neue Server-Objekt im Konstruktor der Klasse UnicastRemoteObject exportiert wird und dabei eine entsprechende Ausnahme auftreten kann. Damit ein Server-Objekt benutzt werden kann, muss es in einem Server erzeugt werden und in einem Namensdienst registriert werden. Der folgende Quelltext realisiert einen HelloServer für ein HelloServantObjekt. // hello/HelloServer.java import HelloServant; import java.rmi.Naming; public class HelloServer { public static void main(String args[]) { try { // Create the server object. HelloServant hello = new HelloServant(); // Register the server object in the RMI registry. Naming.rebind("//localhost/HelloServant", hello); } catch (Exception e) { System.err.println("HelloServer: " + e.toString()); System.exit(1); } } } Bemerkungen: • In der Methode main() wird zunächst ein Security Manager installiert, sofern noch kein Security Manager installiert worden ist. Der Security Manager kontrolliert, welche Klassen zur Laufzeit geladen werden dürfen. 267 • Der Konstruktor der Klasse HelloServant exportiert das Server-Objekt automatisch. Damit Benutzer einfach eine Referenz auf das Server-Objekt bekommen können, wird das Server-Objekt im RMI Namensdienst registriert. Ein einfacher Client, der ein Server-Objekt benutzt, hat folgende Struktur: // hello/HelloClient.java import Hello; import java.rmi.Naming; public class HelloClient { public static void main(String args[]) { String message; try { Hello hello = (Hello) Naming.lookup("//localhost/HelloServant"); message = hello.sayHello(); System.out.println(message); } catch (Exception e) { System.err.println("HelloClient: " + e.toString()); System.exit(1); } } } Bemerkungen: • 268 KAPITEL 15. REMOTE METHOD INVOCATION (RMI) Kapitel 16 Java Name and Directory Interface (JNDI) Namensdienste zur Zuordnung und Auflösung von Namen zu Objekten sind grundlegend für jedes verteilte System. Es haben sich mittlerweile viele Namensdienste etabliert (z.B. DNS, LDAP, NIS, CORBA Naming Service), die alle ähnliche Dienste anbieten. Das Java Name and Directory Interface (JNDI) [27] definiert eine universelle Programmierschnittstelle, mit der auf beliebige Namens- und Verzeichnisdienste zugegriffen werden kann (Virtualisierung). Java Application JNDI API JNDI Naming Manager JNDI SPI CORBA Naming RMI LDAP NDS NIS Abbildung 16.1: JNDI Architektur Durch die Benutzung von der JNDI API sind Anwendungen nicht an spezifische Namens- und Verzeichnisdienste gebunden. Außerdem ist es relativ einfach mit Hilfe des JNDI Service Provider Interface (SPI) möglich, neue Namens- und Verzeichnisdienste einzuführen, ohne bestehende Anwendungen ändern zu müssen. 16.1 JNDI-Terminologie Zunächst ist es wichtig, die JNDI-Terminologie zu verstehen: • Ein atomarer Name (atomic name) ist ein nicht zerlegbarer Teil eines zusammengesetzten Namens. 269 270 KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI) • Ein zusammengesetzter Name (compound name) ist eine Folge von atomaren Namen. • Die Zuordnung eines Namens zu einem Objekt wird als Bindung binding bezeichnet. • Ein Kontext (context) ist ein Objekt, das eine Menge von Namensbindungen mit atomaren Namen verwaltet. • Atomare Namen in einem gegebenen Kontext können an andere Teil-Kontexte (subcontext) gebunden werden. • Ein Namensystem (naming system) ist eine Menge verbundener Kontext desselben Typs mit denselben Operationen. • Ein Namensraum (name space) ist die Menge aller Namen in einem Namensystem. • Ein gemischter Name composite name ist ein Name, der aus zusammengesetzten Namen mehrerer Namensysteme besteht. • Alle Namen sind relativ zu einem Kontext. Es gibt entsprechend keine absoluten Namen. Ein URL wie beispielsweise http://www.inf.uos.de/schoenw/index.html ist ein gutes Beispiel für einen gemischten Namen composite name der drei Namensysteme benutzt. Der erste Teil http ist ein atomarer Name aus dem Namensraum der URL Schemata Identifikatoren. Der zweite Teil www.inf.uos.de ist ein zusammengesetzter Name aus dem DNS Namensraum. Dieser zusammengesetzte Name wird von rechts nach links interpretiert. Der dritte Teil schoenw/index.html ist ein zusammengesetzter Name in einem Namensraum eines Dateisystems der von links nach rechts interpretiert wird. Noch etwas mehr JNDI-Terminologie: • Ein Verzeichnisobjekt (directory object) ist ein Objekttyp mit einer Menge assoziierter Attribute, der Informationen in einem verteilten System repräsentiert. • Ein Attribut (attribute) eines Verzeichnisobjekts besteht aus einem Identifier und einer Menge von Werten. • Ein Verzeichnisobjekt stellt Methoden zur Erzeugen und Löschen von Attributen und zum Verändern von Attributewerten bereit. Offenbar wird ein Verzeichnisdienst als eine Erweiterung eines Namensdienstes modelliert. Zusätzlich unterstützt JNDI die Benachrichtung von Anwendungen über Veränderungen durch spezielle Ereignisse. LDAP Version 3 wird als weit verbreiteter Verzeichnisdienst besonders unterstützt. Entsprechend besteht die JNDI Programmierschnittstelle aus folgenden vier Paketen: 1. javax.naming Grundlegende Klassen und Schnittstellen zum Zugriff auf Namensdienste. 2. javax.naming.directory Erweiterung der grundlegenden Klassen und Schnittstellen zum Zugriff auf Verzeichnisdienste. 3. javax.naming.event Klassen und Schnittstellen zur Benachrichtigung über Änderungen in Namens- und Verzeichnisdiensten. 4. javax.naming.ldap Spezifische Erweiterungen für LDAP Version 3 Verzeichnisse. Das Paket javax.naming.spi definiert die Schnittstelle, mit der neue Namens- und Verzeichnisdienste integriert werden können. 271 16.2. ZUGRIFF AUF NAMENSDIENSTE «interface» «interface» «interface» Enumeration Serializable Cloneable «interface» Context «interface» «interface» NamingEnumeration Name +close(): void +createSubcontext(name:Name): Context +destroySubcontext(name:Name): void +bind(name:Name,obj:Object): void +rebind(name:Name): void +lookup(name:Name): Object +rename(old:Name,new:Name): void +unbind(name:Name): void +list(name:Name): NamingEnumeration +listBindings(name:Name): NamingEnumeration +hasMore(): boolean +next(): Object +close(): void CompoundName «interface» NameParser Object +parse(name:String): Name CompositeName InitialContext NameClassPair Binding Abbildung 16.2: JNDI Klassen und Interfaces 16.2 Zugriff auf Namensdienste Die Abbildung 16.2 zeigt die wesentlichen JNDI Klassen und Interfaces zum Zugriff auf Namensdienste. Bemerkungen: • Alle Methoden basieren auf einem Kontext-Objekt. Operationen, die einen Namen als Parameter benutzen, lösen zuerst den Namen zu einem Kontext-Objekt auf. • Zu den Methoden, die einen Name Parameter haben, gibt es in der Regel eine entsprechende Methode mit einem String Parameter, wobei die Zeichenkette dann zunächst in einen Name Parameter konvertiert wird. • Die Klasse InitialContext erlaubt es, einen initialen Kontext für ein Namensystem zu bekommen. Das folgende Beispiel demonstriert, wie mit Hilfe eines JNDI Service Providers für das lokale Dateisystem Dateinamen aufgelöst werden können. // jndi/Lookup.java import import import import import java.io.File; java.util.Hashtable; javax.naming.Context; javax.naming.InitialContext; javax.naming.NamingException; public class Lookup { 272 KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI) public static void main(String[] args) { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); env.put(Context.PROVIDER_URL, "file:/"); if (args.length > 0) { try { Context ctx = new InitialContext(env); for (int i = 0; i < args.length; i++) { File f = (File) ctx.lookup(args[i]); System.out.println(f); } ctx.close(); } catch (NamingException e) { System.err.println("Lookup failed: " + e); System.exit(1); } } } } Bemerkungen: • Der Service Provider wird über eine Hashtabelle ausgewählt und parametrisiert. • Der initiale Kontext muss explizit durch den Aufruf der Methode close freigegeben werden, damit eventuell gebundene Ressourcen freigegeben werden. Das folgende Beispiel demonstriert, wie man durch die in einem Kontext bekannten Namen iterieren kann. // jndi/List.java import import import import import import import java.util.Hashtable; javax.naming.Context; javax.naming.InitialContext; javax.naming.NamingException; javax.naming.NamingEnumeration; javax.naming.NameClassPair; javax.naming.Binding; public class List { public static void main(String[] args) { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); env.put(Context.PROVIDER_URL, "file:/"); try { 16.3. ZUGRIFF AUF VERZEICHNISDIENSTE 273 Context ctx = new InitialContext(env); // List only the names... NamingEnumeration list = ctx.list("bin"); while (list.hasMore()) { NameClassPair nc = (NameClassPair)list.next(); System.out.println(nc); } // List the names together with the actual bindings... NamingEnumeration bindings = ctx.listBindings("bin"); while (bindings.hasMore()) { Binding bd = (Binding)bindings.next(); System.out.println(bd.getName() + ": " + bd.getObject()); } ctx.close(); } catch (NamingException e) { System.err.println("List failed: " + e); System.exit(1); } } } Bemerkungen: • Die Methode list eines Kontexts liefert einen Enumerator für alle im Kontext bekannten Namen. • Die Methode listBindings eines Kontexts liefert alle Bindungen inklusive der gebundenen Objekte. • Der Aufwand der beiden Methoden kann abhängig vom Service Provider deutlich variieren. Das Erzeugen von neuen Bindungen oder Kontexten erfolgt mit den entsprechenden Methoden des Context Interface. 16.3 Zugriff auf Verzeichnisdienste Ein Verzeichnis ist in JNDI eine Erweiterung eines Namensdienstes, wobei jedem Namen eine Menge von Attributen zugeordnet werden können. Mit Hilfe von Suchprimitiven lassen sich dann aus dem Verzeichnis bestimmte Einträge ermitteln. Die Abbildung 16.3 zeigt die wesentlichen Klassen und Interfaces zum Zugriff auf Verzeichnisse. Das Lesen von Attributen kann beispielsweise folgendermassen realisiert werden: // java/LookupAttributes.java import import import import import import java.util.Hashtable; javax.naming.Context; javax.naming.NamingException; javax.naming.directory.Attributes; javax.naming.directory.DirContext; javax.naming.directory.InitialDirContext; 274 KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI) «interface» Context «interface» DirContext +getAttributes(name:Name): Attributes +modifyAttributes(name:Name,op:int,attrs:Attributes): void +search(name:Name,matchingAttributes:Attributes): NamingEnumeration +search(name:Name,filterExpr:String,filterArgs:Obejct[],cons:SearchControls): NamingEnumeration «interface» Attribute +add(value:Object): boolean +set(index:int,value:Object): Object +remove(index:int): Object +remove(value:Object): boolean +contains(value:Object): boolean +clear(): void +size(): int «interface» Attributes +get(attrID:String): Attribute +getAll(): NamingEnumeration +size(): int +put(attr:Attribute): Attribute +remove(attrID:String): Attribute Object BasicAttribute BasicAttributes InitialDirContext Abbildung 16.3: JNDI Klassen und Interfaces für Verzeichnisdienste public class LookupAttributes { public static void main(String[] args) { Hashtable env = new Hashtable(11); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=uos"); try { DirContext ctx = new InitialDirContext(env); String[] attrIDs = { "sn", "telephonenumber", "golfhandicap", "mail" }; Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs); // GetattrsAll.printAttrs(answer); ctx.close(); } catch (NamingException e) { System.err.println("Lookup failed: " + e); System.exit(1); 16.3. ZUGRIFF AUF VERZEICHNISDIENSTE 275 } } } Bemerkungen: • LDAP Filter für Suchanfragen sind in RFC 2254 [12] definiert, z.B. (&(sn=Geisel)(mail=*)). • Mit Hilfe der SearchControls kann die Ergebnismenge und die Bearbeitung der Suchanfrage parametrisiert werden. 276 KAPITEL 16. JAVA NAME AND DIRECTORY INTERFACE (JNDI) Kapitel 17 Enterprise Java Beans Mit den Enterprise Java Beans (EJBs) hat Sun eine Java-spezifische Komponentenarchitektur definiert, die die Komposition von verteilten Anwendungen aus Standard-Komponenten unterstützt. EJBs erlauben einen verteilten Entwicklungsprozess, bei dem zwischen den Herstellern von Komponenten, den Herstellern von Ablaufumgebungen, den Erstellern von spezifischen Anwendungen und den Betreibern von Anwendungen unterschieden wird. EJBs sind am ehesten mit der CORBA-Technologie vergleichbar, da viele der CORBA-Dienste (z.B. Transaktionsunterstützung) auch in der EJB-Umgebung existieren, wenn auch in einer Java-spezifischen Form. Enterprise Java Beans sind nicht mit den Java Beans zu verwechseln. EJBs sind Komponenten, die typischerweise Teile einer Anwendungslogik repräsentieren. Es geht bei EJBs weniger um die Manipulation von komplexen Objekten einer graphischen Benutzungsoberfläche. 17.1 Eigenschaften von EJBs Zunächst die wichtigsten Eigenschaften von EJBs: • Nur ein einziges EJB-Objekt stellt die Schnittstelle für eine EJB nach außen zur Verfügung (Facade Design Pattern). • EJBs benötigen einen EJB-Container der EJBs beherbergt, Aufrufe entgegennimmt und von EJBs ausführen lässt. • EJB-Container stellen zusätzliche Dienste bereit (z.B. Transaktionen, Sicherheit). • EJB-Container können sofern erforderlich EBJs ein- und auslagern. Dazu muss jede EJB das Interface Serializable implementieren. EJBs können in Session Beans und Entity Beans eingeteilt werden. Session Beans • Session Beans implementieren die Ablauflogik einer Anwendung. • Die Lebenszeit einer Session Bean erstreckt sich typischerweise über eine Sitzung, die mehrere Client/ServerInteraktionen umfasst. (Das Session-Konzept ähnelt den Sessions der Servlets.) • Eine aktive Session Bean ist immer einem Client zugeordnet - Beans bearbeiten niemals Anfragen mehrerer Clients nebenläufig. 277 278 KAPITEL 17. ENTERPRISE JAVA BEANS • Inaktive Session Beans sind entsprechend keinem Client zugeordnet und können bei Bedarf zugeordnet werden. • Zustandslose Session Beans besitzen keinen internen Zustand. Sie können in Pools verwaltet werden und dann sehr schnell einem Client zugeordnet werden. • Zustandsbehaftete Session Beans müssen in der Regel serialisiert werden um Sie einem anderen Client zuzuordnen. Entity Beans • Entity Beans werden zur Modellierung der Daten einer Anwendung verwendet. • Entity Beans realisieren in der Regel eine objekt-orientierte Sicht auf die zugrundeliegende Datenbank. • Entity Beans sind in der Regel persistent. Die Verwaltung der Persistenz kann entweder von der Bean selbst vorgenommen werden (Bean-Managed Persistent Entity Bean) oder aber von dem Container (Container-Managed Persistent Entity Bean). 17.2 Eigenschaften eines Containers Ein Container stellt die Infrastruktur für verteilte Anwendungen bereit. Die von einem Container angebotenen Funktionen sind sehr ähnlich zu den CORBA-Services. Die Standardeigenschaften eines Containers sind: • Ressourcen- und Lebenszyklusmanagement Zuständig für die Verwaltung der Betriebsmittel und das Erzeugen, neuer Instanzen bzw. die Entfernung von inaktiven Instanzen aus dem Speicher. • Zustandsverwaltung Verwaltung von Zuständen für die Dauer einer logischen Kommunikationsbeziehung. • Transaktionsmanagement Unterstützung für die Abwicklung von Transaktionen zwischen mehreren beteiligten EJBs. • Sicherheit Definition von Sicherheitsklassen für EJBs, die die spezifischen Rechte bei der Ausführung einer EJB festlegen. • Datenpersistenz Serialisierung und Speicherung des Zustands einer EJB um ihn zu einem späteren Zeitpunkt wieder rekonstruieren zu können. • Entfernter Zugriff und Ortstransparenz Netzwerkweiter Zugriff auf die Funktionalität von EJBs wobei Benutzer keine genauen Informationen über den Ort der laufenden EBJs benötigen. • Helferwerkzeuge Werkzeuge zur automatischen Erzeugung von Quelltexten. Weitere optionale Eigenschaften sind: • Lastbalancierung Austausch von Lastinformationen zwischen Containern mit dem Ziel, die Last auszugleichen. • Dynamischer Austausch von Komponenten zur Laufzeit Austausch von EJBs zwischen Containern zur Laufzeit um z.B. Systeme für Wartungsarbeiten evakuieren zu können. 279 17.3. AUFBAU EINER EJB • Verteilte Transaktionen Unterstützung von Transaktionen über Container- und Servergrenzen hinweg. • Integrierte XML-Unterstützung Erleichterung der Datenrepräsentation mit Hilfe der XML-Standards. • Integration von CORBA Erweiterungen zur einfacheren Integration von CORBA und EJB Komponenten. 17.3 Aufbau einer EJB Die wesentlichen Komponenten einer EJB sind: • Enterprise Java Bean Implementation • EJB-Objekt samt Remote Interface • Home-Objekt samt Home Interface • Verwaltungsinformationen (Deployment Descriptors) • EJB Jar-Datei EJB Container aClient 4 Call EJBObject aEJBObject 5. Call EJB aEJBInternalObjekt 3. EJBObject Reference 2. Create EJBObject 1. EJBObject? aHomeObject Abbildung 17.1: Zusammenspiel zwischen Home-Objekt und EJB-Objekt Prinzipieller Ablauf beim Zugriff auf eine EJB (Abbildung 17.1): • Zunächst erfolgt eine Lokalisierung des Home-Objekts der EJB über einen Namensdienst (siehe JNDI). • Vom Home-Objekt kann eine Referenz auf eine Instanz eines EJB-Objekts angefordert werden. • Das Home-Objekt erzeugt eine passende Instanz und schickt die Referenz an den Client. • Der Client kann über das EJB-Objekt auf die eigentliche Implementation der EJB zugreifen. Die Schnittstellen sind wie folgt in Java definiert (Abbildung 17.2): 280 KAPITEL 17. ENTERPRISE JAVA BEANS «interface» Serializable «interface» EnterpriseBean «interface» EntityBean «interface» SessionBean +setSesionContext(ctx:SessionContext): void +ejbRemove(): void +ejbActivate(): void +ejbPassivate(): void +setEntityContext(ctx:EntityContext): void +unsetEntityContext(): void +ejbRemove(): void +ejbActivate(): void +ejbPassivate(): void +ejbLoad(): void +ejbStore(): void «interface» Remote «interface» EJBObject +getEJBHome(): EJBHome +getPrimaryKey(): Object +remove(): void +getHandle(): Handle +isIdentical(obj:EJBObject): boolean «interface» EJBHome +remove(handle:Handle): void +remove(primaryKey:Object): void +getEJBMetaData(): EJBMetaData +getHomeHandle(): HomeHandle Abbildung 17.2: Enterprise Java Beans Interfaces Kapitel 18 Java Database Connectivity (JDBC) Mit Hilfe der Java Database Connectivity API (JDBC) können Java Programme Operationen auf relationalen Datenbanken ausführen, die die deskriptive Structured Query Language (SQL) als Datenbanksprache unterstützen. Für die verschiedenen Implementationen von relationalen Datenbanken gibt es passende Treiber, die den Zugriff für eine konkrete Datenbank-Implementation realisieren. 18.1 Relationale Datenbanken Relationale Datenbanken [6] sind in den 70er Jahren entwickelt worden. Sie basieren auf einer formalen Relationen-Algebra und unterstützen im Gegensatz zu anderen Ansätzen deskriptive nicht-prozedurale Anfragesprachen wie SQL. Die praktische Betrachtungsweise von Relationen sind Tabellen: • Eine Tabelle (Relation) enthält mehrere Datensätze (Zeilen, Tupel). • Ein Datensatz besitzt mehrere Attribute, die sich aus den Spalten der Tabelle ergeben. • Um einem Datensatz eindeutig identifizieren zu können benötigt man einen Primärschlüssel (primary key). Ein Primärschlüssel kann aus einer Kombination von Attributen bestehen. • Verweise auf Datensätze in anderen Relationen werden durch Attribute realisiert, deren Werte eindeutig Datensätze in einer anderen Tabelle (Relation) identifizieren (Fremdschlüssel). Anfragen können als Terme der Relationenalgebra interpretiert werden und setzen sich aus folgenden Grundoperationen zusammen: • Vereinigung: Die Vereinigung von zwei Objekt-Mengen mit gleichem Schema. • Differenz: Die Differenz von zwei Objekt-Mengen mit gleichem Schema. • Kartesisches Produkt: Die Bildung aller Kombinationen zweier Objekt-Mengen. • Projektion: Die Auswahl und Vertauschung von Spalten. • Selektion: Die Auswahl von Zeilen einer Objekt-Menge, die einer gegebenen Bedingung genügen. Die Bedingung können logische Operatoren, relationale Operatoren und Konstanten benutzt werden. • Umbenennung: Umbenennung der Attributnamen in einer Menge. • Natürlicher Verbund: Bildung der Kombinationen von zwei Objekt-Mengen anhand gemeinsamer Attribute. 281 282 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) Bemerkungen: • Mit den Termen der Relationenalgebra ist es nicht möglich, zu einer zweistelligen Relation R(x, y) die transitive Hülle R∗ = {(x, y)|∃x1 , . . . , xk : (x, x1 ) ∈ R ∧ (x1 , x2 ) ∈ R ∧ . . . ∧ (xk , y) ∈ R} zu berechnen. • Bei der Auswertung von Termen sind umfangreiche Optimierungen möglich. In der Regel werden daher Terme (Anfragen) zunächst von einem Optimierer umgeschrieben, bevor sie ausgewertet werden. 18.2 Structured Query Language Die wesentlichen Elemente der Structured Query Language (SQL) sollen an einem Beispiel demonstriert werden, das aus zwei Tabellen (Relationen) besteht: name Riese Klein Meier Weiland vorname Adam Eva Max Walter matrikelnr 123456 123457 214321 105432 Tabelle 18.1: Tabelle studenten mit dem Primärschlüssel matrikelnr matrikelnr 123456 123456 123457 fach Informatik Mathematik Informatik note 1.2 3.4 2.3 Tabelle 18.2: Tabelle fachnote mit dem Primärschlüssel matrikelnr,fach Die Befehle der Sprache SQL werden allgemein in drei Bereiche unterteilt: • Data Definition Language (DDL) • Data Manipulation Language (DML) • Data Control Language (DCL) 18.2.1 Data Definition Language Erzeugen von Tabellen Das Erzeugen von neuen Tabellen geschieht mit Hilfe des CREATE TABLE Befehls. Die Tabellen studenten und fachnote werden beispielsweise mit folgenden SQL-Befehlen erzeugt: 18.2. STRUCTURED QUERY LANGUAGE 283 CREATE TABLE studenten(name VARCHAR(20), vorname VARCHAR(20), matrikelnr INT NOT NULL PRIMARY KEY) CREATE TABLE fachnoten(matrikelnr INT NOT NULL, fach VARCHAR(20) NOT NULL, note DECIMAL(2,1)) Löschen von Tabellen Tabellen können mit Hilfe von DROP TABLE wieder entfernt werden. Beispielsweise würden die Befehle DROP TABLE studenten DROP TABLE fachnote die soeben erzeugten Tabellen wieder löschen. Vorsicht: Nach einem DROP TABLE sind alle in der Tabelle enthaltenen Daten unwiderruflich gelöscht. Ändern von Tabellen Mit Hilfe des ALTER TABLE Befehls kann eine existierende Tabelle verändert werden. Beispielsweise können Spalten hinzugefügt oder gelöscht werden. 18.2.2 Data Manipulation Language Die Data Manipulation Language stellt Befehle bereit, mit denen Datensätze eingefügt, gelöscht, verändert und ausgelesen werden können. Einfügen von Datensätzen Der Befehl INSERT INTO erlaubt das Einfügen von neuen Datensätzen. Dabei werden entweder die Werte eines Datensatzes passend zur Struktur einer Tabelle angegeben oder man gibt explizit vor, welche Spalten mit Werten belegt werden sollen. INSERT INSERT INSERT INSERT INTO INTO INTO INTO studenten studenten studenten studenten VALUES VALUES VALUES VALUES (’Riese’, ’Adam’, 123456) (’Klein’, ’Eva’, 123457) (’Meier’, ’Max’, 214321) (’Weiland’, ’Walter’, 105432) INSERT INTO fachnoten VALUES (123456, ’Informatik’, 1.2) INSERT INTO fachnoten VALUES (123456, ’Mathematik’, 3.4) INSERT INTO fachnoten VALUES (123457, ’Informatik’, 2.3) Löschen von Datensätzen Der Befehl DELETE FROM zum Löschen von Datensätzen arbeitet auf Mengen. Gelöscht werden alle Datensätze, die der Bedingung in der WHERE-Klausel entsprechen. Will man genau einen Datensatz löschen, so ist der entsprechende Schlüssel in der WHERE-Klausel zu benutzen. 284 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) DELETE FROM studenten WHERE matrikelnr = 123456 Man beachte, dass dieser Befehl unter Umständen die Datenbank in einem inkonsistenten Zustand hinterlässt. Es existieren jetzt nämlich potentiell noch Einträge in der Tabelle fachnoten zu dem nun nicht mehr existierenden Studenten. Verändern von Datensätzen Datensätze können mit Hilfe des UPDATE-Befehls aktualisiert werden. Eine WHERE-Klausel selektiert wiederum die Datensätze, die verändert werden sollen. UPDATE studenten SET name = ’Mueller’, vorname = ’Ben’ WHERE matrikelnr = 135322 Anfragen Anfragen zum Auslesen von Datensätzen können beliebige Terme der Relationen-Algebra verwenden. Allerdings ist die Anfragesyntax des Befehls SELECT weniger formal als die zugrundeliegende Relationenalgebra. • Beispiel für eine Selektion: SELECT * FROM studenten WHERE matrikelnr = 123456 • Beispiel für eine Projektion: SELECT vorname, name FROM studenten • Beispiel für einen Verbund: SELECT vorname, name, fach, note FROM studenten, fachnoten WHERE studenten.matrikelnr = fachnoten.matrikelnr • Zusätzlich können Ergebnismengen aufsteigend (ASC) oder absteigend (DESC) sortiert werden: SELECT vorname, name, fach, note FROM studenten, fachnoten WHERE studenten.matrikelnr = fachnoten.matrikelnr ORDER BY note ASC 18.2.3 Data Control Language Die Data Control Language dient zur Steuerung von Transaktionen, mit denen mehrere Datenbankzugriffe als eine atomare Einheit behandelt werden können. Tritt während der Durchführung einer Transaktion ein Fehler auf, so kann die Transaktion mit Hilfe des ROLLBACK-Befehls abgebrochen werden. Die Datenbank wird daraufhin alle in der Transaktion durchgeführten Änderungen zurücknehmen. Wird die Transaktion erfolgreich durchgeführt, so wird die Transaktion mit Hilfe des COMMIT-Befehls abgeschlossen. Alle Änderungen werden nun endgültig in den neuen Datenbankzustand übernommen. 18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC 18.3 SQL Zugriffe aus Java mit JDBC -- MySQL dump 8.22 --- Host: localhost Database: infoc ---------------------------------------------------------- Server version 3.23.54 --- Table structure for table ’fachnoten’ -CREATE TABLE fachnoten ( matrikelnr int(11) NOT NULL default ’0’, fach varchar(20) NOT NULL default ’’, note decimal(2,1) default NULL ) TYPE=MyISAM; --- Dumping data for table ’fachnoten’ -- INSERT INTO fachnoten VALUES (123456,’Informatik’,1.2); INSERT INTO fachnoten VALUES (123456,’Mathematik’,3.4); INSERT INTO fachnoten VALUES (123457,’Informatik’,2.3); --- Table structure for table ’studenten’ -CREATE TABLE studenten ( name varchar(20) default NULL, vorname varchar(20) default NULL, matrikelnr int(11) NOT NULL default ’0’, PRIMARY KEY (matrikelnr) ) TYPE=MyISAM; --- Dumping data for table ’studenten’ -- INSERT INSERT INSERT INSERT INTO INTO INTO INTO studenten studenten studenten studenten VALUES VALUES VALUES VALUES (’Riese’,’Adam’,123456); (’Klein’,’Eva’,123457); (’Meier’,’Max’,214321); (’Weiland’,’Walter’,105432); // jdbc/studenten1/Studenten.java import java.sql.Connection; import java.sql.Statement; 285 286 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) import import import import import import import java.sql.PreparedStatement; java.sql.ResultSet; java.sql.DriverManager; java.sql.SQLException; java.lang.ClassNotFoundException; java.lang.InstantiationException; java.lang.IllegalAccessException; public class Studenten { private Connection con = null; /** * Establish a connection to the database. */ public void connect(String url) throws SQLException, ClassNotFoundException, InstantiationException, java.lang.IllegalAccessException { disconnect(); // ensure we are not connected ... Class.forName("com.mysql.jdbc.Driver").newInstance(); con = DriverManager.getConnection(url); } /** * Disconnect from a database. */ public void disconnect() throws SQLException { if (con != null) con.close(); } /** * This method uses a Statement to show the students in the * database. */ public void show() throws SQLException { final String query = "SELECT name, vorname, matrikelnr FROM studenten"; Statement stmt; ResultSet rs; stmt = con.createStatement(); rs = stmt.executeQuery(query); while (rs.next()) { System.out.println(rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getInt(3)); 18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC 287 } stmt.close(); } /** * This method uses a PreparedStatement to insert a bunch * of students into the database. */ public void insert(String[] name, String[] vorname, int[] matrikelnr) throws SQLException { final String template = "INSERT INTO studenten(name, vorname, matrikelnr) " + "VALUES (?, ?, ?)"; PreparedStatement stmt; stmt = con.prepareStatement(template); for (int i = 0; i < name.length; i++) { stmt.setString(1, name[i]); stmt.setString(2, vorname[i]); stmt.setInt(3, matrikelnr[i]); stmt.executeUpdate(); } stmt.close(); } /** * This method uses a batch of PreparedStatement to remove a * bunch of students from the database. */ public void remove(int[] matrikelnr) throws SQLException { final String template = "DELETE FROM studenten WHERE matrikelnr = ?"; PreparedStatement stmt; stmt = con.prepareStatement(template); for (int i = 0; i < matrikelnr.length; i++) { stmt.setInt(1, matrikelnr[i]); stmt.addBatch(); } stmt.executeBatch(); stmt.close(); } /** * This method uses a ResultSet from a Statement which allows to * modify the result set and to write back modified rows to the * database. */ 288 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) public void update(String[] name, String[] vorname, int[] matrikelnr) throws SQLException { final String query = "SELECT name, vorname, matrikelnr FROM studenten"; Statement stmt; ResultSet rs; stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); rs = stmt.executeQuery(query); for (int i = 0; i < name.length; i++) { rs.beforeFirst(); while (rs.next()) { if (rs.getInt(3) == matrikelnr[i]) { rs.updateString(1, name[i]); rs.updateString(2, vorname[i]); rs.updateRow(); break; } } } stmt.close(); } public static void main(String[] args) throws Exception { final String url = "jdbc:mysql://localhost/infoc?user=infoc&password=$infoc!"; String[] name = { "Kafka", "Mueller", "Schreiner", "Kaufmann" }; String[] vorname = { "Franz", "Martin", "Corina", "Claudia" }; int[] matrikelnr = { 123456, 234567, 345678, 456789 }; Studenten s = new Studenten(); s.connect(url); System.out.println("\nOriginal state of the database:\n"); s.show(); s.remove(matrikelnr); System.out.println("\nIntermediate state of the database:\n"); s.show(); s.insert(name, vorname, matrikelnr); System.out.println("\nFinal state of the database:\n"); s.show(); s.disconnect(); } } // jdbc/studenten2/Studenten.java 18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC import import import import import import import import import 289 java.sql.Connection; java.sql.Statement; java.sql.PreparedStatement; java.sql.ResultSet; java.sql.DriverManager; java.sql.SQLException; java.lang.ClassNotFoundException; java.lang.InstantiationException; java.lang.IllegalAccessException; public class Studenten { private Connection con = null; /** * Establish a connection to the database. */ public void connect(String url) throws SQLException, ClassNotFoundException, InstantiationException, java.lang.IllegalAccessException { disconnect(); // ensure we are not connected ... Class.forName("com.mysql.jdbc.Driver").newInstance(); con = DriverManager.getConnection(url); } /** * Disconnect from a database. */ public void disconnect() throws SQLException { if (con != null) con.close(); } /** * Start a new transaction by disabling automatic commit. */ public void startTransaction() throws SQLException { con.setAutoCommit(false); } /** * Commit the current transaction and reenable automatic commit. */ public void commitTransaction() throws SQLException { 290 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) con.commit(); con.setAutoCommit(true); } /** * Abort the current transaction by rolling back and reenable * automatic commit. */ public void abortTransaction() throws SQLException { con.rollback(); con.setAutoCommit(true); } /** * This method uses a Statement to show the students in the * database. */ public void show() throws SQLException { final String query = "SELECT name, vorname, matrikelnr FROM studenten"; Statement stmt; ResultSet rs; stmt = con.createStatement(); rs = stmt.executeQuery(query); while (rs.next()) { System.out.println(rs.getString(1) + "\t" + rs.getString(2) + "\t" + rs.getInt(3)); } stmt.close(); } /** * This method uses a PreparedStatement to insert a bunch * of students into the database. */ public void insert(String[] name, String[] vorname, int[] matrikelnr) throws SQLException { final String template = "INSERT INTO studenten(name, vorname, matrikelnr) " + "VALUES (?, ?, ?)"; PreparedStatement stmt; stmt = con.prepareStatement(template); for (int i = 0; i < name.length; i++) { 18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC 291 stmt.setString(1, name[i]); stmt.setString(2, vorname[i]); stmt.setInt(3, matrikelnr[i]); stmt.executeUpdate(); } stmt.close(); } /** * This method uses a batch of PreparedStatement to remove a * bunch of students from the database. */ public void remove(int[] matrikelnr) throws SQLException { final String template = "DELETE FROM studenten WHERE matrikelnr = ?"; PreparedStatement stmt; stmt = con.prepareStatement(template); for (int i = 0; i < matrikelnr.length; i++) { stmt.setInt(1, matrikelnr[i]); stmt.addBatch(); } stmt.executeBatch(); stmt.close(); } /** * This method uses a ResultSet from a Statement which allows to * modify the result set and to write back modified rows to the * database. */ public void update(String[] name, String[] vorname, int[] matrikelnr) throws SQLException { final String query = "SELECT name, vorname, matrikelnr FROM studenten"; Statement stmt; ResultSet rs; stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); rs = stmt.executeQuery(query); for (int i = 0; i < name.length; i++) { rs.beforeFirst(); while (rs.next()) { if (rs.getInt(3) == matrikelnr[i]) { rs.updateString(1, name[i]); rs.updateString(2, vorname[i]); 292 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) rs.updateRow(); break; } } } stmt.close(); } public static void main(String[] args) throws Exception { final String url = "jdbc:mysql://localhost/infoc?user=infoc&password=$infoc!"; String[] name = { "Kafka", "Mueller", "Schreiner", "Kaufmann" }; String[] vorname = { "Franz", "Martin", "Corina", "Claudia" }; int[] matrikelnr = { 123456, 234567, 345678, 456789 }; Studenten s = new Studenten(); s.connect(url); System.out.println("\nOriginal state of the database:\n"); s.show(); s.startTransaction(); s.remove(matrikelnr); System.out.println("\nIntermediate state of the database:\n"); s.abortTransaction(); s.show(); s.insert(name, vorname, matrikelnr); System.out.println("\nFinal state of the database:\n"); s.show(); s.disconnect(); } } Fragen zur Selbstkontrolle 1. Einführung (a) Nennen Sie Eigenschaften von Software und erläutern Sie inwiefern sich Software von anderen technischen Produkten unterscheidet. (b) Definieren Sie den Begriff Software-Engineering (Software-Technik). (c) Was versteht man unter der Softwarekrise und was sind die typischen Problemquellen? (d) Was sind fehlertolerante Systeme? Was ist der Unterschied zwischen einem Fehler und einem Ausfall? (e) Erläutern Sie den Begriff Software-Qualität. Welche Qualitätsmerkmale kennen Sie? 2. Prozessmodelle (a) Wozu dient das Capability Maturity Model (CMM)? (b) Erläutern Sie den wesentlichen Unterschied zwischen dem Wasserfallmodell und dem V-Modell. (c) Beschreiben Sie den Unterschied zwischen einem horizontalen und einem vertikalen Prototyp und zwischen einem Demontrationsprototyp und einem Pilotsystem. (d) Nennen Sie die wesentlichen Techniken des extremen Programmierens. (e) Welches Prozessmodell würden Sie bei der Entwicklung einer Software zur Verwaltung von Bibliotheksbeständen inklusive einer Web-basierten Ausleihfunktion einsetzen? 3. Unified Modeling Language (a) Nennen Sie mindestens 5 Diagrammarten der Unified Modeling Language. (b) Wie werden in Klassendiagramme Sichtbarkeiten und Gültigkeitsbereiche notiert? (c) Was versteht man unter Generalisierung, Spezialisierung, Aggregation und Komposition? Welche Darstellungsformen werden benutzt? (d) Was versteht man unter einer rekursiven Assoziation? Geben Sie ein Beispiel für eine rekursive Assoziation an. (e) Erläutern Sie den Begriff der Multiplizität. (f) Was sind Assoziationsklassen? (g) Entwerfen Sie ein Klassendiagramm und ein passendes Objektdiagramm für eine sehr einfache Bibliothek. (h) Entwerfen Sie ein Sequenzdiagramm für den Vorgang Auto tanken“. ” (i) Was ist ein Kollaborationsdiagramm? In welchem Verhältnis steht es zu einem Sequenzdiagramm? (j) Beschreiben Sie in einem Aktivitätsdiagramm die Zubereitung einer Pizza. Welche Aktivitäten können nebenläufig ausgeführt werden? Wozu kann man Swimlanes“ benutzen? ” (k) Erklären Sie die Begriffe Komponenten- und Verteilungsdiagramm. 293 294 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) 4. Entwurfsmuster (a) Was versteht man unter einem Entwurfsmuster und in welcher Form werden Entwurfsmuster beschrieben? (b) Beschreiben Sie das Entwurfsmuster Composite. (c) Was versteht man unter einem Observer? Skizzieren Sie das Prinzip in Form eines UML Diagramms. Kennen Sie Realisierungen des Observer Entwurfsmusters? (d) Welche Bedeutung hat das Strategy Entwurfsmuster? Wann kann man es sinnvoll einsetzen? (e) Was versteht man unter einer Chain of Responsibility? (f) Erläutern Sie das Model-View-Controller Entwurfsmuster. 5. Qualitätssicherung (a) Nennen Sie typische Klassen von Softwarefehlern. (b) Was ist eine Programminspektion? (c) Erklären Sie den Unterschied zwischen Black Box und White Box Testverfahren. (d) Was versteht man unter funktionalen Testverfahren? Wozu dienen die Äquivalenzklassen? (e) Erklären Sie anhand eines kleinen Beispiels den Begriff des Kontrollflußgraphen. (f) Im Zusammenhang mit Kontrollfluß-orientierten Verfahren spielen die Begriffe Anweisungsüberdeckung, Zweigüberdeckung, Bedingungsüberdeckung und Pfadüberdeckung eine wichtige Rolle. Erklären Sie die Begriffe sowie das Verhältnis zueinander. (g) Wozu dienen Regressionstests? (h) Erläutern Sie den Unterschied zwischen statischen und dynamischen Software-Metriken. (i) Was versteht man unter dem Fan-in bzw. dem Fan-out einer Funktion oder Methode? (j) Auf welcher Basis ist die zyklomatische Komplexität definiert? (k) Welche Konstrukte eines Quelltextes werden bei den Halstead-Metriken analysiert? (l) Nennen Sie typische Indikatoren für schlecht riechenden“ Quelltext. ” 6. Werkzeuge (a) Welche Werkzeuge zur Programmentwicklung kennen Sie? (b) Was versteht man unter Versionsmanagement? (c) Im Zusammenhang mit den Systemen RCS und CVS spricht man von Revisionen, Varianten und Konfigurationen. Erläutern Sie diese Begriffe an einem geeigneten Beispiel. (d) Nennen Sie die wesentlichen Unterschiede zwischen RCS und CVS. (e) Wozu dienen Testwerkzeuge? Nennen Sie Beispiele. (f) Was versteht man unter einer integrierten Entwicklungsumgebung? Welche Vorteile besitzen integrierten Entwicklungsumgebungen? 7. Ergonomische Aspekte (a) Was versteht man unter einem Benutzermodell? (b) Was sind modale Dialoge und wann sollte man modale Dialoge verwenden? (c) Erklären Sie die Begriffe Primärdialog und Sekundärdialog. (d) Was ist der Unterschied zwischen einer funktionsorientierten und einer objektorientierten Bedienung? (e) Erklären Sie die Begriffe Aufgabenangemessenheit, Selbstbeschreibungsfähigkeit, Steuerbarkeit, Erwarungskonformität, Fehlertoleranz, Individualisierbarkeit und Lernförderlichkeit. (f) Welche Regeln sollte man bei der Gestaltung von Dialogen beachten? 18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC 295 8. Java Abstract Windowing Toolkit (AWT) (a) Was versteht man unter ereignisorientierter Programmierung? (b) Erläutern Sie an einem kleinen Beispiel, wie ein Java Programm auf den Eintritt eines Ereignisses reagieren kann. (c) Was versteht man unter containment und durch welche Klassen wird dieses Konzept realisiert? 9. Java Foundation Classes (Swing) (a) Wodurch unterscheiden sich Top-Level-Container von anderen Containern in der Swing Klassenbibliothek? (b) Welche Aufgaben erledigt ein Layout Manager? Was ist der Zusammenhang zwischen minimumLayoutSize, preferredLayoutSize und maximumLayoutSize? (c) Erläutern Sie den Algorithmus hinter dem Border Layout Manager. (d) Vergleichen Sie den Box Layout Algorithmus mit dem Flow Layout Algorithmus. (e) Der Grid Bag Layout Algorithmus benutzt Informationen aus GridBadConstraints Objekten um das Layout zu berechnen. Nennen Sie einige der Parameter und beschreiben Sie deren Wirkung. Was versteht man in diesem Zusammenhang under padding? (f) Erläutern Sie an einem Beispiel wie ein einfaches Menü implementiert wird. (g) Was versteht man im Zusammenhang von Menüs unter Actions? Welchen Vorteil bietet die Benutzung von Actions? (h) Mit welchen Java-Elementen lassen sich Ausschnitte einer Komponente darstellen, die mit einem Rollbalken manipuliert werden können? (i) Nach welchem Prinzip erfolgt die Darstellung von Dokumenten in Java? Wie werden Änderungen an einem Dokument propagiert? (j) Welche Standard-Dialoge und -Komponenten sind Ihnen bekannt und wofür kann man sie benutzen? (k) Bei der Darstellung von Graphiken wird ein User Space Koordinatensystem und ein Device Space Koordinatensystem benutzt. Erklären Sie den Unterschied. (l) Was versteht man unter einem rendering context? 10. Java Beans (a) Was versteht man unter einer Software-Komponente? Wie verwendet man Software-Komponenten effektiv? (b) Erklären Sie an einem kleinen Beispiel was Properties von Java Beans sind. Was versteht man unter einer Zugriffsmethode? (c) Erläutern Sie die Begriffe Simple Property, Indexed Property, Bound Property und Constrained Property. (d) Wie werden Constrained Properties implementiert? Erklären Sie den Ablauf beim Verändern von Constrained Properties. (e) Welche Aufgabe hat eine BeanInfo Klasse? 11. Servlets (a) Erklären Sie die Begriffe Uniform Resource Locator (URL), Uniform Resource Name (URN) und Uniform Resource Identifier (URI). (b) Nennen Sie die wichtigsten Methoden des HTTP Protokolls und erläutern Sie, wie die Statuscodes aufgebaut sind. (c) Erläutern Sie den Begriff Servlet. Was ist der Unterschied zwischen einem generischen Servlet und einem HTTP Servlet? 296 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) (d) Was versteht man im Zusammenhang mit Servlets unter einer Session? Nennen Sie mindestens zwei Methoden mit denen Sessions bei mehreren HTTP-Methodenaufrufen identifiziert werden können. (e) Wie können Zustandsinformationen an eine Session gebunden werden und wielange existieren Sessions im Web-Server? (f) Was sind JavaServer Pages? Welche Vor- und Nachteile haben JavaServer Pages gegenüber einfachen Java Servlets? (g) Durch den Einsatz von Beans läßt sich die Trennung der Anwendungslogik von der Darstellung der Information in HTML weiter verbessern. Welche wichtige Rolle spielen in diesem Zusammenhang die Properties einer Bean? 12. Extensible Markup Language (XML) (a) Aus welchen grundlegenden Strukturelementen bestehen XML-Dokumente? (b) Was versteht man unter einem wohlgeformten XML-Dokument? In welchem Verhältnis steht ein wohlgeformtes XML-Dokument zu einem validierten XML-Dokument? (c) Vergleichen Sie Document Type Definitions (DTDs) mit XML Schemas? Nennen Sie Beispiele, die sich im mächtigeren Ansatz sehr wohl ausdrücken lassen, aber nicht im weniger mächtigern Ansatz darstellbar sind. (d) Was versteht man unter dem Document Object Model (DOM)? 13. Verteilte Anwendungen (a) Erläutern Sie den Begriff Client/Server-Modell und nennen Sie einige Beispiele. (b) Was versteht man unter einem Remote Procedure Call (RPC)? Wozu dienen die Stub-Prozeduren? (c) Nennen Sie Ihnen bekannte RPC-Semantiken und erklären Sie die Unterschiede. (d) Was versteht man unter N-Tier Architekturen? Geben Sie jeweils ein Beispiel für eine 2-Tier, eine 3-Tier und eine 4-Tier Architektur an. 14. CORBA (a) Skizzieren sie die Object Management Architecture (OMA). Was ist der Unterschied zwischen Common Object Services und Common Facilities? (b) Aus welchen Komponenten besteht ein Object Request Broker (ORB) und welche Funktionen haben sie? (c) Welche primäre Funktion erfüllt die Interface Definition Language (IDL)? (d) Was versteht man unter einer discriminated union? (e) Welche Bedeutung haben in, out und inout Parameter in IDL-Operationen? (f) Erläutern Sie den Begriff Objekt-Referenz. Wozu dienen Objekt-Referenzen? (g) Welche Aufgabe hat der Naming Service? Wie ist der Namensraum oganisiert? (h) Skizzieren Sie ein Java Programm zum Durchlaufen eines kompletten Namensraums. (i) Welche Aufgabe haben Holder- und Helper-Klassen, die bei der IDL-Übersetzung nach Java erzeugt werden? 15. Remote Method Invocation (RMI) (a) Vergleichen Sie Java RMI mit CORBA. Was sind die wesentlichen Vor- und Nachteile? Was sind die Gemeinsamkeiten? (b) Was muss getan werden, damit Methoden eine Java-Klasse via RMI von anderen Programmen aufgerufen werden können? 16. Java Name and Directory Interface 18.3. SQL ZUGRIFFE AUS JAVA MIT JDBC 297 (a) Erklären Sie den Unterschied zwischen einem Namensdienst und einem Verzeichnisdienst. (b) Nennen Sie Beispiele für zusammengesetzte Namen (compound names) und gemischte Namen (composite names). (c) Was ist ein JNDI Kontextobjekt (Context) und welche Methoden stellt es zur Verfügung? 17. Enterprise Java Beans (a) Welche Aufgabe erfüllt ein EJB-Objekt und auf welchem Design Pattern basiert es? (b) Erläutern Sie an einer Skizze, wie ein Client eine EJB-Instanz lokalisiert und mit ihr kommuniziert. (c) Erklären Sie den Unterschied zwischen Entity und Session Beans. (d) Nennen Sie mindestens fünf Aufgaben, die durch einen Container unterstützt werden. 18. Datenbankzugriffe mit JDBC (a) Welche Grundoperationen gibt es in der Relationenalgebra? (b) Nennen Sie eine Relation, die nicht mit Termen der Relationenalgebra berechnet werden kann. (c) Geben Sie eine SQL-Anfrage an, die die Namen aller Studenten liefert, die in Informatik eine Note besser oder gleich 2.0 haben. (d) Was versteht man im Zusammenhang mit JDBC unter einem Batch? Wann sind Batches sinnvoll? (e) Vergleichen Sie einfache Anfragen (Statement) mit vorbereiteten Anfragen (PreparedStatement). (f) Was versteht man unter einer Transaktion und wie wird eine Transaktion in JDBC realisiert? 298 KAPITEL 18. JAVA DATABASE CONNECTIVITY (JDBC) Literaturverzeichnis [1] H. Balzert. Lehrbuch der Software-Technik. Spektrum, Akademischer Verlag, 2 edition, 2000. [2] K. Beck. Extreme Programming Explained: Embrace Change. Addison-Wesley, 1999. [3] G. Booch, J. Rumbaugh, and I. Jacobsen. The Unified Modeling Language User Guide. Addison Wesley, 1998. [4] T. Bray, J. Paoli, and C. M. Sperberg-McQueen. Extensible Markup Language (XML) 1.0. W3C Recommendation, Textuality and Netscape, Microsoft, University of Illinois, February 1998. [5] F. P. Brooks. The Mythical Man-Month. Addison Wesley, 1975. [6] C.J. Date. An Introduction to Database Systems. Addison-Wesley, 4 edition, 1986. [7] T. DeMarco. Der Termin. Hanser Verlag, 1998. [8] K. Beck E. Gamma. Junit a cook’s tour. Technical report, 1998. [9] K. Beck E. Gamma. Junittest infected: Programmers love writing tests. Java Report, 3(7):51–58, 1998. [10] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable ObjectOriented Software. Addison Wesley, 1995. [11] Michael Herczeg. Software-Ergonomie: Grundlagen der Mensch-Computer-Kommunikation. Addison Wesley, 1994. [12] T. Howes. The String Representation of LDAP Search Filters. RFC 2254, Netscape Communications Corp., December 1997. [13] ISO. Information processing systems – Text and Office Systems – Standard Generalized Markup Language (SGML). International Standard ISO 8879, ISO, 1986. [14] B.P. Miller, L. Fredriksen, and B. So. An empirical study of the reliability of UNIX utilities. Communications of the ACM, 33(12):32–44, 1990. [15] G.J. Myers. The Art of Software Testing. John Wiley & Sons, 1979. [16] Object Management Group. Unified Modeling Language Specification Version 1.3. Formal Specification, Object Management Group, March 2000. [17] Object Management Group. Unified Modeling Language Specification Version 1.4. Formal Specification, Object Management Group, September 2001. [18] B. Oestereich. Objektorientierte Softwareentwicklung: Analyse und Design mit der Unified Modeling Language. Oldenbourg, 4 edition, 1998. [19] OMG. IDL to Java Language Mapping Specification. Technical Report Revision 1.1, Object Management Group, June 2001. [20] OMG. The Common Object Request Broker: Architecture and Specification. Technical Report Revision 2.6, Object Management Group, December 2001. [21] R. Reißing. Extremes Programmieren. Informatik Spektrum, 23(2):118–121, 2000. 299 300 LITERATURVERZEICHNIS [22] W. Schneider. Ergonomische Anforderungen für Bürotätigkeiten mit Bildschirmgeräten – Grundsätze der Dialoggestaltung (Kommentar zu DIN EN ISO 9241-10). Beuth Verlag, 1998. [23] B. Shneiderman. Designing the User Interface: Strategies for Effective Human-Computer Interaction. Addison Wesley, 3 edition, 1997. [24] J. Siegel. CORBA Fundamentals and Programming. Wiley & Sons, 1996. [25] I. Sommerville. Software Engineering. Addison Wesley, 2001. [26] P. Stevens and R. Pooley. UML - Softwareentwicklung mit Objekten und Komponenten. Addison Wesley, 2000. [27] Sun. Java Naming and Directory Interface Application Programming Interface (JNDI API). Technical Report 1.2, Sun Microsystems, July 1999. [28] K. Walrath and M. Campione. The JFC Swing Tutorial: A Guide to Constructing GUIs. Addison Wesley, 3 edition, 1999. [29] A. Zeller and J. Krinke. Programmierwerkzeuge. dpunkt, 2000.