Skript "Software-Engineering1" < @Prof. Dr. Peter Edelmann > 1 ARCHITEKTUR GRAFISCHER BENUTZUNGSOBERFLÄCHEN ............................................... 11 1.1 AWT ........................................................................................................................................................ 11 1.1.1 Beispiel Calculator ......................................................................................................................... 11 1.1.2 AWT und Swing ............................................................................................................................. 12 1.1.2.1 AWT........................................................................................................................................... 12 1.1.2.2 Swing ......................................................................................................................................... 12 1.1.2.3 Überblick über AWT Components ............................................................................................. 13 1.1.3 Erstellen eines normalen Fensters oder eines Dialogs ................................................................. 13 1.1.3.1 Beispiel zum Erzeugen eines Frames ....................................................................................... 13 1.1.3.2 Aktionen zum Erstellen .............................................................................................................. 14 1.1.3.2.1 Anlegen eines Dialogfensters .............................................................................................. 14 1.1.3.2.2 Zuordnen eines Layoutmanagers ........................................................................................ 14 1.1.3.2.3 Einfügen von Dialogelementen ............................................................................................ 14 1.1.3.2.4 Anzeigen des Dialogfensters ............................................................................................... 15 1.1.3.3 Beispiel Anpassen eines Frames .............................................................................................. 15 1.1.4 Eventmodell ................................................................................................................................... 16 1.1.4.1 Beispiel ActionEvent .................................................................................................................. 16 1.1.4.2 Überblick .................................................................................................................................... 16 1.1.4.3 Ereignisse .................................................................................................................................. 17 1.1.4.3.1 Diagramm............................................................................................................................. 17 1.1.4.3.2 Primitive Ereignisse.............................................................................................................. 17 1.1.4.3.3 Ereignisse zur Interaktion verschiedener Komponenten ...................................................... 18 1.1.4.4 Listener ...................................................................................................................................... 18 1.1.4.4.1 Konzept ................................................................................................................................ 18 1.1.4.4.2 Listenerdiagramm ................................................................................................................ 18 1.1.4.4.3 Listener-Schnittstellen .......................................................................................................... 18 1.1.4.5 Ereignisquellen .......................................................................................................................... 19 1.1.4.5.1 Konzept ................................................................................................................................ 19 1.1.4.5.2 Ereignisquellen .................................................................................................................... 19 1.1.4.6 Aktivierung von Lauschobjekten ................................................................................................ 19 1.1.4.7 Beispiel MouseEventsDemoEventTechniken ............................................................................ 20 1.1.4.7.1 Implementation Listener durch Frame ................................................................................. 20 1.1.4.7.2 Adapter................................................................................................................................. 21 1.1.4.7.3 Anonyme Klasse .................................................................................................................. 21 1.1.4.8 Adapter Klassen ........................................................................................................................ 22 1.1.4.9 Adapter als innere Klassen ........................................................................................................ 22 1.1.4.10 Beispiele ................................................................................................................................ 23 1.1.4.10.1 Beispiel Schließen eines Frames mit Escape .................................................................... 23 1.1.4.10.1.1 Implementation Keylistener ......................................................................................... 23 1.1.4.10.1.2 Innerere Klasse und Adapter ....................................................................................... 23 1.1.4.10.1.3 Anonyme Klasse .......................................................................................................... 24 1.1.4.10.1.4 Überlagern der Event-Handler in den Komponenten................................................... 24 1.1.4.10.2 Key Events ......................................................................................................................... 26 1.1.4.10.3 Mouse Events ..................................................................................................................... 29 1.1.4.10.4 Component Events ............................................................................................................. 29 1.1.4.10.5 Focus Events...................................................................................................................... 29 1.1.5 Layoutmanager .............................................................................................................................. 29 1.1.5.1 Beispiel Layout .......................................................................................................................... 29 1.1.5.2 Konzept ..................................................................................................................................... 30 1.1.5.3 FlowLayout ................................................................................................................................ 30 1.1.5.4 GridLayout ................................................................................................................................. 31 1.1.5.5 BorderLayout ............................................................................................................................. 31 1.1.5.6 CardLayout ................................................................................................................................ 32 1.1.5.7 GridBagLayout ........................................................................................................................... 32 1.1.5.8 Null-Layout ................................................................................................................................ 32 1.1.6 Modale Dialoge .............................................................................................................................. 32 1.1.6.1 Beispiel Dialoge ......................................................................................................................... 32 1.1.6.2 Warten auf die Eingabe ............................................................................................................. 33 1.1.6.3 Beispiel VariableDialoge ............................................................................................................ 33 1.2 SWING ...................................................................................................................................................... 33 1.2.1 Beispiel Geburtsmonat .................................................................................................................. 33 1.2.2 Probleme des AWT ....................................................................................................................... 35 1.2.3 Leichtgewichtige Komponenten ..................................................................................................... 35 1.2.4 Pluggable Look-and-Feel .............................................................................................................. 35 1.2.5 Das Model-View-Controller-Prinzip ................................................................................................ 35 1.2.6 Container und Menüs .................................................................................................................... 36 1.2.6.1 Hauptfenster .............................................................................................................................. 36 1.2.6.1.1 Beispiel MDI ......................................................................................................................... 36 2 1.2.6.1.2 JFrame ................................................................................................................................. 37 1.2.6.1.2.1 Struktur .......................................................................................................................... 37 1.2.6.1.2.2 Erzeugung ..................................................................................................................... 37 1.2.6.1.2.3 Zugriff ............................................................................................................................ 37 1.2.6.1.3 JInternalFrame ..................................................................................................................... 38 1.2.6.1.3.1 Konzept ......................................................................................................................... 38 1.2.6.1.3.2 Der Desktop .................................................................................................................. 38 1.2.6.1.3.3 Die Kindfenster .............................................................................................................. 38 1.2.6.1.4 Beispiel SplashScreen ........................................................................................................ 38 1.2.6.1.5 JWindow .............................................................................................................................. 38 1.2.6.1.6 JDialog ................................................................................................................................. 39 1.2.6.1.7 Beispiel Quartal .................................................................................................................... 39 1.2.6.1.8 JOptionPane ........................................................................................................................ 39 1.2.6.1.9 JApplet ................................................................................................................................. 39 1.2.6.2 Menüs ........................................................................................................................................ 40 1.2.6.2.1 Einfache Menüs ................................................................................................................... 40 1.2.6.2.1.1 Beispiel SimpleMenue ................................................................................................... 40 1.2.6.2.1.2 Grundlagen von Swing-Menüs ...................................................................................... 41 1.2.6.2.2 Weitere Möglichkeiten .......................................................................................................... 41 1.2.6.2.2.1 Beispiel Extended Menue ( funktioniert nicht mit VisualCafe) ....................................... 41 1.2.6.2.2.2 Untermenüs ................................................................................................................... 42 1.2.6.2.2.3 Icons in Menüeinträgen ................................................................................................. 42 1.2.6.2.2.4 Checkboxes und Radiobuttons in Menüeinträgen ......................................................... 43 1.2.6.2.3 Kontextmenue ...................................................................................................................... 43 1.2.6.2.3.1 Beispiel Kontextmenue .................................................................................................. 43 1.2.6.2.3.2 Kontextmenüs ............................................................................................................... 43 1.2.7 Swing Komponenten ..................................................................................................................... 43 1.2.7.1 Einfache Swing Komponenten................................................................................................... 43 1.2.7.1.1 Beispiel Swing Components ................................................................................................ 43 1.2.7.1.2 Label und Textfelder ............................................................................................................ 48 1.2.7.1.2.1 JLabel ............................................................................................................................ 48 1.2.7.1.2.2 JTextField ...................................................................................................................... 48 1.2.7.1.2.3 JPasswordField ............................................................................................................. 48 1.2.7.1.2.4 JTextArea ...................................................................................................................... 49 1.2.7.1.3 Buttons ................................................................................................................................. 49 1.2.7.1.3.1 JButton .......................................................................................................................... 49 1.2.7.1.3.2 JCheckBox .................................................................................................................... 49 1.2.7.1.3.3 JRadioButton ................................................................................................................. 49 1.2.7.1.4 Listen und Comboboxen ...................................................................................................... 49 1.2.7.1.4.1 JList ............................................................................................................................... 50 1.2.7.1.4.2 JComboBox ................................................................................................................... 50 1.2.7.1.5 Quasi-analoge Komponenten .............................................................................................. 50 1.2.7.1.5.1 JScrollBar ...................................................................................................................... 50 1.2.7.1.5.2 JSlider ........................................................................................................................... 51 1.2.7.2 Jtree ........................................................................................................................................... 52 1.2.7.2.1 Beispiel SelectionTree ......................................................................................................... 52 1.2.7.2.2 Bäume .................................................................................................................................. 53 1.2.7.2.3 Interfaces für das Model-View Konzept................................................................................ 53 1.2.7.2.4 Default-Implementationen der Interfaces ............................................................................. 54 1.2.7.2.5 Erzeugen eines Baumes ...................................................................................................... 55 1.2.7.2.6 Selektieren von Knoten ........................................................................................................ 55 1.2.7.2.7 Beispiel MutableTree ........................................................................................................... 56 1.2.7.2.8 Verändern der Baumstruktur ................................................................................................ 57 1.2.7.3 JTabbedPane ............................................................................................................................ 58 1.2.7.3.1 Beispiel TabbedPane ........................................................................................................... 58 1.2.7.3.2 JTabbedPane ....................................................................................................................... 59 1.3 GRAFIKEN UND BILDER ................................................................................................................................ 59 1.3.1 Grundlagen der Grafikausgabe ..................................................................................................... 59 1.3.1.1 Beispiel PrimitiveRoutines ......................................................................................................... 59 1.3.1.2 Die Methode paint...................................................................................................................... 60 1.3.1.3 Die Klasse Graphics .................................................................................................................. 60 1.3.1.4 Das grafische Koordinatensystem ............................................................................................. 60 1.3.1.5 Elementare Grafikroutinen ......................................................................................................... 61 1.3.1.5.1 Linien ................................................................................................................................... 61 1.3.1.5.2 Rechteck .............................................................................................................................. 61 1.3.1.5.3 Polygon ................................................................................................................................ 61 1.3.1.5.4 Kreis ..................................................................................................................................... 62 1.3.1.5.5 Kreisbogen ........................................................................................................................... 62 1.3.1.6 Linien und Füllmodus ................................................................................................................ 62 1.3.1.7 Kopieren und Löschen von Flächen .......................................................................................... 62 2 3 1.3.1.8 Die Clipping-Region ................................................................................................................... 63 1.3.2 Bitmaps und Animation .................................................................................................................. 63 1.3.2.1 Bitmaps ...................................................................................................................................... 63 1.3.2.1.1 Beispiel BitmapOhneRepaintPaintMechanismus ................................................................. 63 1.3.2.1.2 Laden einer Bitmap .............................................................................................................. 63 1.3.2.1.3 Die Klasse Toolkit ................................................................................................................ 64 1.3.2.1.4 Ausgeben einer Bitmap ........................................................................................................ 64 1.3.2.1.5 Die Klasse MediaTracker ..................................................................................................... 64 1.3.2.1.5.1 Problem ......................................................................................................................... 64 1.3.2.1.5.2 Lösung durch einen MediaTracker ................................................................................ 64 1.3.2.1.5.3 Beschreibungen aus der Online Hilfe ............................................................................ 65 1.3.2.1.6 Verbleibendes Problem ........................................................................................................ 66 1.3.2.1.7 Beispiel Bitmap (Mit repaint-paint Mechanismus) ................................................................ 66 1.3.2.1.8 Der repaint() – paint() Mechanismus .................................................................................... 66 1.3.2.1.8.1 Die Methode paint() ....................................................................................................... 66 1.3.2.1.8.2 Die Methode repaint() .................................................................................................... 66 1.3.2.1.8.3 Die Methode update() .................................................................................................... 67 1.3.2.1.8.4 Anmerkungen ................................................................................................................ 67 1.3.2.1.8.5 Unterschiede zwischen AWT und Swing ....................................................................... 67 1.3.2.1.8.5.1 AWT........................................................................................................................ 67 1.3.2.1.8.5.2 Swing ...................................................................................................................... 68 1.3.2.2 Animation ................................................................................................................................... 68 1.3.2.2.1 Beispiel BitmapAnimation .................................................................................................... 68 1.3.2.2.2 Konzept ................................................................................................................................ 69 1.3.2.2.3 Beispiel FlackerndesImageApplet ........................................................................................ 70 1.3.2.2.4 Reduktion des Bildschirmflackerns ...................................................................................... 70 1.3.2.2.5 Beispiel PartialRemovingImageApplet ................................................................................. 71 1.3.2.2.6 Selektives Löschen .............................................................................................................. 72 1.3.2.2.7 Beispiel DoubleBufferingImageApplet.................................................................................. 72 1.3.2.2.8 Doppelpufferung................................................................................................................... 73 1.3.2.2.9 Beispiel ExclusiveOrImageApplet ........................................................................................ 73 1.3.2.2.10 Löschen mit Exclusiv-Oder................................................................................................. 74 1.4 OBJEKTSERIALISERUNG............................................................................................................................... 74 1.4.1 Serialisierung von einfachen Objekten .......................................................................................... 74 1.4.1.1 Beispiel Einfache Serialisierung ................................................................................................ 74 1.4.1.2 Begriffsbestimmung ................................................................................................................... 75 1.4.1.3 Schreiben von Objekten ............................................................................................................ 75 1.4.1.3.1 Die Klasse ObjectOutputStream .......................................................................................... 75 1.4.1.3.2 Methoden von ObjectOutputStream ..................................................................................... 75 1.4.1.3.3 Konzept ................................................................................................................................ 75 1.4.1.3.4 Das Interface Serializable .................................................................................................... 76 1.4.1.4 Lesen von Objekten ................................................................................................................... 76 1.4.1.5 Anmerkungen ............................................................................................................................ 76 1.4.1.6 Auszug aus der Online Hilfe ...................................................................................................... 77 1.4.1.7 Nicht-serialisierte Membervariablen .......................................................................................... 77 1.4.2 Serialiserung von Graphen ............................................................................................................ 78 1.4.2.1 Beispiel Graph Serialiserung Familie ........................................................................................ 78 1.4.2.2 Objektreferenzen ....................................................................................................................... 79 1.4.2.2.1 Problem ................................................................................................................................ 79 1.4.2.2.2 Lösung ................................................................................................................................. 79 1.4.2.2.3 Warnungen .......................................................................................................................... 79 1.4.3 Serialisierung von Collections ....................................................................................................... 80 1.4.3.1 Beispiel ObjectStore .................................................................................................................. 80 1.4.3.2 Serialisierbare Standardklassen ................................................................................................ 81 1.4.4 Kopieren durch Serialisieren ......................................................................................................... 81 1.4.4.1 Beispiel Kopieren durch Serialisierung ...................................................................................... 82 1.4.4.2 Serialisieren in Byte Array ......................................................................................................... 83 1.5 EXCEPTION HANDLING ................................................................................................................................ 83 1.5.1 Behandlung von Exceptions .......................................................................................................... 83 1.5.1.1 Beispiel Simple Exception ......................................................................................................... 83 1.5.1.2 Die try-catch-Anweisung ............................................................................................................ 84 1.5.1.3 Das Fehlerobjekt ....................................................................................................................... 84 1.5.1.4 Die Fehlerklassen von Java ...................................................................................................... 84 1.5.1.5 Fortfahren nach Fehlern ............................................................................................................ 85 1.5.1.6 Beispiel MultipleCatchException................................................................................................ 86 1.5.1.7 Mehrere catch-Klauseln ............................................................................................................. 86 1.5.1.8 Die finally-Klausel ...................................................................................................................... 86 1.5.2 Weitergabe von Exceptions ........................................................................................................... 87 1.5.2.1 Beispiel Divide ........................................................................................................................... 87 1.5.2.2 Die catch-or-throw-Regel ........................................................................................................... 88 3 4 1.5.2.3 Auslösen von Ausnahmen ......................................................................................................... 88 1.6 GLOBALISIERUNG ....................................................................................................................................... 88 1.6.1 Problem ......................................................................................................................................... 88 1.6.2 Beispiel LocalizedClock ................................................................................................................. 88 1.6.3 Die Klasse Locale .......................................................................................................................... 89 1.6.4 Die Klasse DataFormat ................................................................................................................. 90 1.6.5 Beispiel SimpleGlobalMenue ......................................................................................................... 90 1.6.6 Die Klasse ResourceBundle .......................................................................................................... 91 2 DIE UNIFIED MODELING LANGUAGE (UML) UND DER RATIONAL UNIFIED SOFTWARE DEVELOPMENT PROCESS (RUP) ........................................................................................................ 91 2.1 UML IM ÜBERBLICK .................................................................................................................................... 91 2.1.1 Dokumentation .............................................................................................................................. 91 2.1.1.1 Wozu dient die Dokumentation ................................................................................................. 91 2.1.1.2 Anmerkungen zur Dokumentation ............................................................................................. 91 2.1.2 Geschichte der UML ...................................................................................................................... 92 2.1.3 Views ............................................................................................................................................. 92 2.2 RUP IM ÜBERBLICK .................................................................................................................................... 93 2.3 AKTIVITÄTEN DES RUP UND DIE ZUGEHÖRIGEN UML DIAGRAMME ................................................................... 93 2.3.1 Business Modeling ........................................................................................................................ 93 2.3.2 Requirements ................................................................................................................................ 94 2.3.2.1 Erstellen einer Vision ................................................................................................................. 94 2.3.2.1.1 Beispiel Getränkeautomat .................................................................................................... 94 2.3.2.1.2 Beispiel Bibliothek ................................................................................................................ 94 2.3.2.1.3 Konzept ................................................................................................................................ 94 2.3.2.2 Aufnehmen der Stakeholder Requests ...................................................................................... 94 2.3.2.3 Erstellen eines Use Case Models .............................................................................................. 95 2.3.2.3.1 Beispiel Getränkeautomat .................................................................................................... 95 2.3.2.3.2 Beispiel Bibliothek ................................................................................................................ 95 2.3.2.3.3 Überblick .............................................................................................................................. 96 2.3.2.3.4 System ................................................................................................................................. 96 2.3.2.3.5 Actor ..................................................................................................................................... 96 2.3.2.3.5.1 Charakterisierung .......................................................................................................... 96 2.3.2.3.5.2 Beziehungen zwischen Actors....................................................................................... 97 2.3.2.3.6 Use Case ............................................................................................................................. 97 2.3.2.3.6.1 Charakterisierung .......................................................................................................... 97 2.3.2.3.6.2 Spezifikation .................................................................................................................. 97 2.3.2.3.6.3 Beziehungen zwischen Use Cases ............................................................................... 98 2.3.2.3.7 Erweiterung der UML ........................................................................................................... 98 2.3.2.3.8 Wie findet man Actors und Use Cases ................................................................................ 98 2.3.2.4 Erstellen von Prototypen für User Interfaces ............................................................................. 99 2.3.2.4.1 Beispiel Bibliothek ................................................................................................................ 99 2.3.2.4.2 Getränkeautomat ............................................................................................................... 100 2.3.2.5 Festlegen der Supplementary Requirements .......................................................................... 100 2.3.2.5.1 Beispiel Bibliothek .............................................................................................................. 100 2.3.2.5.2 Beispiel Getränkeautomat .................................................................................................. 100 2.3.2.6 Festlegen der Supplementary Requirements .......................................................................... 101 2.3.2.6.1 Beispiel Bibliothek .............................................................................................................. 101 2.3.2.6.2 Beispiel Getränkeautomat .................................................................................................. 101 2.3.2.6.3 Fragenkatalog für Supplementary Requirements (aus Industrieprojekt für Bildverarbeitung) ................................................................................................................................. 101 2.3.2.6.3.1 Auslieferungstermine/Zeitresourcen ............................................................................ 101 2.3.2.6.3.2 Kostengrenze/Budget/Geldresourcen ......................................................................... 101 2.3.2.6.3.3 Nationale oder Internationale Regularien .................................................................... 101 2.3.2.6.3.4 Conditions ................................................................................................................... 101 2.3.2.6.3.5 Produktsupport ............................................................................................................ 101 2.3.2.6.3.6 Juristische Anforderungen ........................................................................................... 101 2.3.2.6.3.7 Technologische Randbedingungen für die Lösung (Solution Constrains) .................. 101 2.3.2.6.3.8 Anforderungen bzgl. Datenvolumen ............................................................................ 102 2.3.2.6.3.9 Zeitanforderungen/Performance ................................................................................. 102 2.3.2.6.3.10 Anforderungen an die Präzision des Systems ........................................................... 102 2.3.2.6.3.11 Lastanforderungen .................................................................................................... 102 2.3.2.6.3.12 Anforderungen an Fehlertoleranz, Robustheit des Systems ..................................... 102 2.3.2.6.3.13 Anforderungen an die Benutzbarkeit ......................................................................... 102 2.3.2.6.3.14 Anforderungen an die technische Sicherheit bzgl. Anwender und collaborierende Hardware 102 2.3.2.6.3.15 Anforderungen an die Datensicherheit ...................................................................... 102 2.3.2.6.3.16 Anforderungen an die Interoperabilität mit anderen Systemen/ Installationsumgebung (Implementation Environment) ................................................................... 102 4 5 2.3.2.6.3.17 Anforderungen an die Dokumentation (Benutzer+Wartung) ..................................... 103 2.3.2.6.3.18 Anforderungen an die Wartbarkeit ............................................................................ 103 2.3.2.6.3.19 Anforderungen an die Portabilität .............................................................................. 103 2.3.2.7 Erstellen einer Liste für mögliche Anforderungen .................................................................... 103 2.3.2.7.1 Beispiel Bibliothek .............................................................................................................. 103 2.3.2.7.2 Beispiel Getränkeautomat .................................................................................................. 103 2.3.2.8 Erstellen eines Glossary .......................................................................................................... 103 2.3.2.8.1 Beispiel Bibliothek .............................................................................................................. 103 2.3.2.8.2 Beispiel Getränkeautomat .................................................................................................. 103 2.3.2.9 Beispiel Bankautomat .............................................................................................................. 104 2.3.3 Analyse & Design ........................................................................................................................ 104 2.3.3.1 Identifizierung von Klassen und Objekten ............................................................................... 104 2.3.3.1.1 Beispiel Bibliothek .............................................................................................................. 104 2.3.3.1.2 Beispiel Getränkeautomat .................................................................................................. 104 2.3.3.1.3 Grundlegende Probleme .................................................................................................... 105 2.3.3.1.4 Studieren des Problem Domains ....................................................................................... 105 2.3.3.1.4.1 Entity Class ................................................................................................................. 106 2.3.3.1.4.2 Boundary Class ........................................................................................................... 106 2.3.3.1.4.3 Control Class ............................................................................................................... 106 2.3.3.1.5 Studium der sprachlichen Beschreibung des Problems .................................................... 106 2.3.3.1.6 Studium existierender Software ......................................................................................... 107 2.3.3.2 Erstellen eines Class Diagrams ............................................................................................... 107 2.3.3.2.1 Beispiel Getränkeautomat .................................................................................................. 107 2.3.3.2.2 Beispiel Bibliothek .............................................................................................................. 107 2.3.3.2.3 Ziel ..................................................................................................................................... 107 2.3.3.2.4 UML Darstellung der Klassen ............................................................................................ 107 2.3.3.2.5 Identifizieren der Beziehungen zwischen Klassen ............................................................. 108 2.3.3.2.5.1 Association .................................................................................................................. 108 2.3.3.2.5.1.1 Allgemein .............................................................................................................. 108 2.3.3.2.5.1.2 Aggregation .......................................................................................................... 108 2.3.3.2.5.1.3 Composition Aggregation ..................................................................................... 109 2.3.3.2.5.2 Realization ................................................................................................................... 109 2.3.3.2.5.3 Generalization ............................................................................................................. 109 2.3.3.2.5.4 Dependency ................................................................................................................ 109 2.3.3.2.6 Templates .......................................................................................................................... 110 2.3.3.3 Weitere Beispiele .................................................................................................................... 110 2.3.3.4 Erstellen der Use Case Realisierungen ................................................................................... 112 2.3.3.4.1 Beispiel Getränkeautomat .................................................................................................. 112 2.3.3.4.2 Beispiel Bibliothek .............................................................................................................. 112 2.3.3.4.3 Erstellen eines Use Case spezifischen Class Diagrams.................................................... 112 2.3.3.4.4 Erstellen eines Object Diagrams ........................................................................................ 112 2.3.3.4.4.1 Konzept ....................................................................................................................... 112 2.3.3.4.4.2 Associations ................................................................................................................ 112 2.3.3.4.5 Erstellen eines Collaboration Diagram ............................................................................... 113 2.3.3.4.6 Erstellen eines Sequence Diagrams .................................................................................. 113 2.3.3.5 Spezifizierung der Klassen ...................................................................................................... 114 2.3.3.5.1 Beispiel Getränkeautomat .................................................................................................. 114 2.3.3.5.2 Beispiel Bibliothek .............................................................................................................. 114 2.3.3.5.3 Identifizieren von Elementfunktionen und Attributen .......................................................... 114 2.3.3.5.4 Beschreibung der Verantwortlichkeiten der Klassen .......................................................... 116 2.3.3.5.5 Erstellen der Beschreibungen der Schnittstellenfunktion ................................................... 116 2.3.3.5.6 Updaten der Beziehungen zwischen den Klassen ............................................................. 117 2.3.3.5.7 Statechart Diagram ............................................................................................................ 117 2.3.3.5.7.1 Beispiel Getränkeautomat ........................................................................................... 117 2.3.3.5.7.2 Beispiel Bibliothek ....................................................................................................... 117 2.3.3.5.7.3 Konzept ....................................................................................................................... 117 2.3.3.5.7.4 States .......................................................................................................................... 117 2.3.3.5.7.5 Events ......................................................................................................................... 118 2.3.3.5.7.6 Transitions ................................................................................................................... 118 2.3.3.5.8 Activity Diagram ................................................................................................................. 118 2.3.3.5.8.1 Beispiel Getränkeautomat ........................................................................................... 119 2.3.3.5.8.2 Konzept ....................................................................................................................... 119 2.3.3.5.9 Identifizierung aktiver Klassen ........................................................................................... 119 2.3.3.6 Erstellen eines Package Diagrams .......................................................................................... 119 2.3.3.6.1 Beispiel Getränkeautomat .................................................................................................. 119 2.3.3.6.2 Beispiel Bibliothek .............................................................................................................. 120 2.3.3.6.3 Packages ........................................................................................................................... 120 2.3.3.6.4 Beziehungen zwischen Packages ...................................................................................... 121 2.3.3.6.5 Zugriffssteuerung ............................................................................................................... 121 2.3.3.7 Erstellen des Deployment Diagrams ....................................................................................... 121 5 6 2.3.3.7.1 Beispiel Getränkeautomat .................................................................................................. 121 2.3.3.7.2 Beispiel Bibliothek .............................................................................................................. 122 2.3.3.7.3 Konzept .............................................................................................................................. 123 2.3.3.8 Unterschied zwischen Analyse und Design ............................................................................. 123 2.3.3.8.1 Analyse .............................................................................................................................. 123 2.3.3.8.2 Design ................................................................................................................................ 124 2.3.3.9 Weitere Beispiele .................................................................................................................... 124 2.3.4 Implementation ............................................................................................................................ 128 2.3.4.1 Erstellen des Component Diagrams ........................................................................................ 128 2.3.4.1.1 Beispiel Getränkeautomat .................................................................................................. 128 2.3.4.1.2 Beispiel Bibliothek .............................................................................................................. 128 2.3.4.1.3 Konzept .............................................................................................................................. 128 2.3.4.2 Erstellen des Integration Build Plan ......................................................................................... 129 2.3.4.2.1 Beispiel Getraenkeautomat ................................................................................................ 129 2.3.4.2.2 Bottom Up Integration ........................................................................................................ 129 2.3.4.3 Implementieren der Components ............................................................................................ 131 2.3.4.3.1 Ziel ..................................................................................................................................... 131 2.3.4.3.2 Programmierregeln ............................................................................................................ 131 2.3.4.3.2.1 Java Code Conventions (Selbststudium) .................................................................... 131 2.3.4.3.3 Round Trip Engineering ..................................................................................................... 146 2.3.4.3.3.1 Beispiel Bank ............................................................................................................... 146 2.3.4.3.3.2 Beispiel Wertpapier ..................................................................................................... 146 2.3.4.3.3.3 Sinn und Zweck ........................................................................................................... 148 2.3.4.3.3.4 Tutorial für Round Trip Engineering mit Rational Rose ............................................... 148 2.3.4.3.3.4.1 Grundeinstellungen............................................................................................... 148 2.3.4.3.3.4.1.1 Setzen des CLASSPATH ............................................................................... 148 2.3.4.3.3.4.1.2 Anlegen eines Projektverzeichnisses ............................................................ 148 2.3.4.3.3.4.1.3 Starten von Rose ........................................................................................... 148 2.3.4.3.3.4.1.4 Hinzufügen des Projektverzeichnisses zum CLASSPATH ............................ 149 2.3.4.3.3.4.1.5 Speichern des Rose Projektes ....................................................................... 149 2.3.4.3.3.4.2 Vorgehensweise am Beispiel HelloWorld ............................................................. 149 2.3.4.3.3.4.2.1 LogicalView: Erzeugen der Start Klasse ........................................................ 149 2.3.4.3.3.4.2.2 Erzeugen des Codes der Klasse.................................................................... 149 2.3.4.3.3.4.2.3 Bearbeiten des Codes ................................................................................... 150 2.3.4.3.3.4.2.4 ReverseEngineering der HelloWorld Klasse .................................................. 150 2.3.4.3.3.4.2.5 Erzeugen von HelloWorldGUI ........................................................................ 151 2.3.4.3.3.4.2.6 Erzeugen des Codes der Klasse.................................................................... 152 2.3.4.3.3.4.2.7 Bearbeiten des Codes ................................................................................... 152 2.3.4.3.3.4.2.8 ReverseEngineering der HelloWorldGUI Klasse ........................................... 152 2.3.4.4 Integrieren des Systems .......................................................................................................... 153 2.3.5 Test .............................................................................................................................................. 153 2.3.5.1 Beispiel Klassentest MyMath ................................................................................................... 153 2.3.5.1.1 BlackBoxTest ..................................................................................................................... 153 2.3.5.1.1.1 Testling Interface ......................................................................................................... 153 2.3.5.1.1.2 Testapplikation ............................................................................................................ 153 2.3.5.1.1.3 Tracer .......................................................................................................................... 154 2.3.5.1.1.4 TestController .............................................................................................................. 155 2.3.5.1.1.5 Testprozedur ............................................................................................................... 156 2.3.5.1.1.6 Einige Testfälle ............................................................................................................ 156 2.3.5.1.1.6.1 Test calcDurch() ................................................................................................... 156 2.3.5.1.1.6.1.1 Testfall Standarddivision ................................................................................ 156 2.3.5.1.1.6.1.1.1 Voraussetzungen .................................................................................... 156 2.3.5.1.1.6.1.1.2 Eingabe ................................................................................................... 156 2.3.5.1.1.6.1.1.3 Erwartete Ausgabe .................................................................................. 157 2.3.5.1.1.6.1.1.4 Ausgabe .................................................................................................. 157 2.3.5.1.1.6.1.2 Testfall Division durch negative Zahl ............................................................. 157 2.3.5.1.1.6.1.2.1 Voraussetzungen .................................................................................... 157 2.3.5.1.1.6.1.2.2 Eingabe ................................................................................................... 157 2.3.5.1.1.6.1.2.3 Erwartete Ausgabe .................................................................................. 157 2.3.5.1.1.6.1.2.4 Ausgabe .................................................................................................. 157 2.3.5.1.1.6.1.3 Testfall Division durch Null ............................................................................. 157 2.3.5.1.1.6.1.3.1 Voraussetzungen .................................................................................... 157 2.3.5.1.1.6.1.3.2 Eingabe ................................................................................................... 157 2.3.5.1.1.6.1.3.3 Erwartete Ausgabe .................................................................................. 157 2.3.5.1.1.6.1.3.4 Ausgabe .................................................................................................. 157 2.3.5.1.1.6.2 Test calcPotenz() .................................................................................................. 158 2.3.5.1.1.6.2.1 Testfall Standard ............................................................................................ 158 2.3.5.1.1.6.2.1.1 Voraussetzungen .................................................................................... 158 2.3.5.1.1.6.2.1.2 Eingabe ................................................................................................... 158 2.3.5.1.1.6.2.1.3 Erwartete Ausgabe .................................................................................. 158 6 7 2.3.5.1.1.6.2.1.4 Ausgabe .................................................................................................. 158 2.3.5.1.1.6.2.2 Testfall Negativer Exponent ........................................................................... 158 2.3.5.1.1.6.2.2.1 Voraussetzungen .................................................................................... 158 2.3.5.1.1.6.2.2.2 Eingabe ................................................................................................... 158 2.3.5.1.1.6.2.2.3 Erwartete Ausgabe .................................................................................. 158 2.3.5.1.1.6.2.2.4 Ausgabe .................................................................................................. 158 2.3.5.1.1.6.2.3 Testfall Exponent Null .................................................................................... 158 2.3.5.1.1.6.2.3.1 Voraussetzungen .................................................................................... 159 2.3.5.1.1.6.2.3.2 Eingabe ................................................................................................... 159 2.3.5.1.1.6.2.3.3 Erwartete Ausgabe .................................................................................. 159 2.3.5.1.1.6.2.3.4 Ausgabe .................................................................................................. 159 2.3.5.1.2 WhiteBoxTest ..................................................................................................................... 159 2.3.5.1.2.1 Testling ........................................................................................................................ 159 2.3.5.1.2.2 Test Log ...................................................................................................................... 160 2.3.5.1.2.3 Kontrollflussgraph ........................................................................................................ 162 2.3.5.2 Beispiel für Test Guidelines ..................................................................................................... 163 2.3.5.2.1 Klassentest ........................................................................................................................ 163 2.3.5.2.1.1 Ziel............................................................................................................................... 163 2.3.5.2.1.2 Zu testende Klassen .................................................................................................... 163 2.3.5.2.1.3 Black Box Test (Funktionaler Test) ............................................................................. 163 2.3.5.2.1.3.1 Ziel ........................................................................................................................ 163 2.3.5.2.1.3.2 Definition der Testfälle .......................................................................................... 163 2.3.5.2.1.3.2.1 Allgemeine Hinweise ...................................................................................... 163 2.3.5.2.1.3.2.2 Äquivalenzklassenbildung .............................................................................. 163 2.3.5.2.1.3.2.3 Grenzwertanalyse .......................................................................................... 164 2.3.5.2.1.3.2.4 Test spezieller Werte (special values testing)................................................ 165 2.3.5.2.1.3.2.5 Zufallstest ....................................................................................................... 165 2.3.5.2.1.3.2.6 Ableitung von Testfällen aus State Charts ..................................................... 165 2.3.5.2.1.4 White Box Test (Strukturtest, Glass Box Test) ............................................................ 165 2.3.5.2.1.4.1 Ziel ........................................................................................................................ 165 2.3.5.2.1.4.2 Definition der Testfälle beim Kontrollflussorientierte Strukturtestverfahren .......... 165 2.3.5.2.1.4.2.1 Anweisungsüberdeckung ............................................................................... 165 2.3.5.2.1.4.2.2 Zweigüberdeckung ......................................................................................... 166 2.3.5.2.1.4.2.3 Pfadüberdeckung ........................................................................................... 166 2.3.5.2.1.4.2.4 Bedingungsüberdeckung ............................................................................... 166 2.3.5.2.2 Integrationstest .................................................................................................................. 167 2.3.5.2.2.1 Ziel............................................................................................................................... 167 2.3.5.2.2.2 Für welche Gruppen von Klassen ist ein Integrationstest durchzuführen ................... 167 2.3.5.2.2.3 Definition der Testfälle ................................................................................................. 167 2.3.5.2.3 Regressionstest ................................................................................................................. 167 2.3.5.2.4 Systemtest ......................................................................................................................... 167 2.3.5.2.4.1 Ziel............................................................................................................................... 167 2.3.5.2.4.2 Definition der Testfälle ................................................................................................. 167 2.3.5.2.5 Abnahmetest (Acceptance Test) ........................................................................................ 168 2.3.5.2.6 Testdurchführung ............................................................................................................... 168 2.3.5.2.6.1 Testumgebung ............................................................................................................ 168 2.3.5.2.6.2 Durchführung eines kombinierten White und Black Box Test für Klassen .................. 169 2.3.5.2.6.3 Durchführung eines Integrationstests für eine Gruppe von Klassen ........................... 169 2.3.5.2.6.4 Wer führt den Test durch ............................................................................................ 170 2.3.5.2.6.5 Testendekriterium ........................................................................................................ 170 2.3.5.2.6.6 Verwaltung der gefundenen Fehler ............................................................................. 170 2.3.5.2.6.7 Verwaltung der Testumgebung und Testdaten ........................................................... 170 2.3.5.3 Erstellen eines Test Case ........................................................................................................ 170 2.3.5.4 Erstellen einer Test Prozedur .................................................................................................. 171 2.3.5.5 Erstellen eines Test Log .......................................................................................................... 171 2.3.5.6 Erstellen einer Test Suite (vgl. JUnit) ...................................................................................... 171 2.3.5.7 Unit Tests mit dem Test-Tool JUnit ......................................................................................... 171 2.3.5.7.1 Sinn und Zweck .................................................................................................................. 171 2.3.5.7.2 Download und Installation (Selbststudium) ........................................................................ 171 2.3.5.7.3 Ausführung der Tests mittels TestRunner .......................................................................... 172 2.3.5.7.4 Wichtige Klassen des Frameworks .................................................................................... 173 2.3.5.7.5 SingleTestCase .................................................................................................................. 173 2.3.5.7.5.1 Beispiel EuroTestSingleTestCase ............................................................................... 173 2.3.5.7.5.1.1 Requirements ....................................................................................................... 173 2.3.5.7.5.1.2 Schreiben des Testfalles in Form der Testmethode ............................................. 173 2.3.5.7.5.1.3 Schreiben der zu testenden Methode ................................................................... 174 2.3.5.7.5.2 Assert .......................................................................................................................... 174 2.3.5.7.5.3 AssertionFailedError .................................................................................................... 175 2.3.5.7.6 MultipleTestCases .............................................................................................................. 176 2.3.5.7.6.1 Beispiel EuroTestMultipleTestCases ........................................................................... 176 7 8 2.3.5.7.6.2 Definition von mehreren Testfällen in der Klasse EuroTest ........................................ 176 2.3.5.7.6.3 Generierung von EuroTest Instanzen für jeden Testfall .............................................. 177 2.3.5.7.7 TestCaseTree .................................................................................................................... 178 2.3.5.7.7.1 Beispiel AllTests .......................................................................................................... 178 2.3.5.7.7.2 TestSuite ..................................................................................................................... 178 2.3.5.7.8 Exceptions.......................................................................................................................... 178 2.3.5.7.8.1 Testen von Exceptions ................................................................................................ 179 2.3.5.7.8.2 Unerwartete Exceptions .............................................................................................. 179 2.3.5.7.9 Implementationsdetails des Frameworks ........................................................................... 179 2.3.6 Configuration&Change Management .......................................................................................... 184 2.3.6.1 Beispiel für Configuration Management Guidelines................................................................. 184 2.3.6.1.1 Software-Element ............................................................................................................... 184 2.3.6.1.2 Version ............................................................................................................................... 184 2.3.6.1.3 Checkin-Checkout Modell .................................................................................................. 184 2.3.6.1.4 Konfiguration (Lineup) und Release (Baseline) ................................................................. 185 2.3.6.1.5 Configuration Specification (Konfigurations-Identifikationsdokument KID) ........................ 185 2.3.6.1.6 Single Stream Versioning................................................................................................... 185 2.3.6.1.7 Varianten von Versionen .................................................................................................... 185 2.3.6.1.8 Branches ............................................................................................................................ 186 2.3.6.1.9 Parallel Stream Versioning................................................................................................. 186 2.3.6.1.10 File based System ............................................................................................................ 187 2.3.6.1.11 View based System .......................................................................................................... 187 2.3.6.1.12 Zustandsübergänge bei der Bearbeitung eines Software-Elementes .............................. 187 2.3.6.2 Beispiel Configuration Management mit CVS (Tutorial, Selbststudium) .................................. 187 2.3.6.2.1 Struktur des Projektes ........................................................................................................ 187 2.3.6.2.2 Allgemeine Vorbereitungen ................................................................................................ 188 2.3.6.2.2.1 Download von CVS ..................................................................................................... 188 2.3.6.2.2.2 Installation von CVS .................................................................................................... 188 2.3.6.2.2.3 Setzen des Classpath für den Taschenrechner .......................................................... 188 2.3.6.2.2.4 Anlegen des Projektverzeichnisses ............................................................................. 188 2.3.6.2.2.5 Anlegen des Repository .............................................................................................. 189 2.3.6.2.2.6 Setzen der CVSROOT Variablen ................................................................................ 189 2.3.6.2.2.7 Sonderbehandlung von Binärdateien einstellen .......................................................... 189 2.3.6.2.3 Erstellen des Main Trunk durch den Projektleiter .............................................................. 190 2.3.6.2.3.1 Anlegen des Projektes ................................................................................................ 190 2.3.6.2.3.2 Erzeugen des Package rechner .................................................................................. 191 2.3.6.2.3.3 Erzeugen des Interfaces ............................................................................................. 191 2.3.6.2.3.4 Erzeugen einer Dummy Implementation des Interfaces ............................................. 192 2.3.6.2.3.5 Aufnehmen der Dateien in das Projekt ........................................................................ 192 2.3.6.2.3.6 Einchecken der Dateien .............................................................................................. 192 2.3.6.2.3.7 Labeln der Konfiguration ............................................................................................. 192 2.3.6.2.3.8 Erzeugen das Package gui und der Datei TaschenRechnerGUI ............................... 192 2.3.6.2.3.9 Erzeugen der Main Klasse TaschenRechner.java ...................................................... 193 2.3.6.2.3.10 Anlegen der Branches ............................................................................................... 193 2.3.6.2.3.11 Zugriff auf zentrales Repository ermöglichen ............................................................ 194 2.3.6.2.4 Entwickeln der GUI durch Entwickler1 ............................................................................... 194 2.3.6.2.4.1 Auschecken der Arbeitskopie vom Branch Entwickler-1-GUI...................................... 194 2.3.6.2.4.2 Codieren der GUI Klasse ............................................................................................ 194 2.3.6.2.4.3 Einchecken der Arbeitskopie in den Branch Entwickler-1-GUI.................................... 197 2.3.6.2.5 Entwickeln des Rechners durch Entwickler2 ..................................................................... 197 2.3.6.2.5.1 Auschecken der Arbeitskopie vom Branch Entwickler-2-Rechner .............................. 197 2.3.6.2.5.2 Codieren der Rechnerlogik – Datei TaschenRechnerImpl.java................................... 197 2.3.6.2.5.3 Codieren der GUI Klasse als Testtreiber ..................................................................... 198 2.3.6.2.5.4 Einchecken der Arbeitskopie in den Branch Entwickler-2-Rechner ............................ 199 2.3.6.2.6 Zusammenführen (Mergen) der Entwicklungsstränge durch den Projektleiter ................. 199 2.3.6.2.7 Darstellung der Entwicklungsstränge ................................................................................. 200 2.3.6.3 Beispiel Configuration Management Unterstützung durch Rational Rose ............................... 202 2.3.6.3.1 Controlled Units.................................................................................................................. 202 2.3.6.3.2 Verwalten von controlled units ........................................................................................... 202 2.3.6.4 Bereitstellen eines Project Repository ..................................................................................... 203 2.3.6.5 Erstellen von Base Lines ......................................................................................................... 203 2.3.6.6 Erstellen von Change Requests .............................................................................................. 203 2.3.7 Deployment ................................................................................................................................. 203 2.3.7.1 Erstellen von Training Material ................................................................................................ 203 2.3.7.2 Erstellen von User Support Material ........................................................................................ 204 2.3.7.3 Erstellen von Release Notes ................................................................................................... 204 2.3.7.4 Ausliefern an ß-Tester ............................................................................................................. 204 2.3.8 Project Management ................................................................................................................... 204 2.3.8.1 Erstellen einer Risk List ........................................................................................................... 204 2.3.8.2 Erstellen eines Iteration Plan ................................................................................................... 204 8 9 2.3.8.3 Aquire Staff .............................................................................................................................. 204 2.3.8.4 Erstellen von Work Orders ...................................................................................................... 204 2.3.9 Environment ................................................................................................................................ 205 2.3.9.1 Erstellen von Templates .......................................................................................................... 205 2.3.9.2 Erstellen von Guidelines .......................................................................................................... 205 2.3.9.3 Bereitstellen von Tools ............................................................................................................ 205 2.4 PHASEN DES RUP.................................................................................................................................... 205 2.4.1 Zitate zum Begriff Komplexität ..................................................................................................... 205 2.4.2 Die Struktur komplexer Systeme ................................................................................................. 205 2.4.3 Grundlegende Konzepte des RUP .............................................................................................. 206 2.4.3.1 Use Case Driven...................................................................................................................... 206 2.4.3.2 Architecture Centric ................................................................................................................. 207 2.4.3.3 Iterative and Incremental ......................................................................................................... 207 2.4.3.4 Strukturierung des Entwicklungsprozesses ............................................................................. 207 2.4.3.4.1 Phasen-Aktivitäten Diagramm ............................................................................................ 207 2.4.3.4.2 Disciplines .......................................................................................................................... 208 2.4.3.4.3 Zyklen................................................................................................................................. 208 2.4.3.4.4 Phasen ............................................................................................................................... 208 2.4.3.4.5 Milestones .......................................................................................................................... 209 2.4.4 Phasen des RUP und zugehörige primäre Aktivitäten................................................................. 209 2.4.4.1 Inception .................................................................................................................................. 209 2.4.4.2 Elaboration .............................................................................................................................. 210 2.4.4.3 Construction ............................................................................................................................ 211 2.4.4.4 Transition ................................................................................................................................. 212 3 KOMPONENTENBASIERTE SOFTWAREENTWICKLUNG AM BEISPIEL VON ENTERPRISE JAVA BEANS ....................................................................................................................................... 212 3.1 LITERATUR .............................................................................................................................................. 212 3.2 INTRODUCTION ......................................................................................................................................... 212 3.2.1 Distributed Object Architectures .................................................................................................. 213 3.2.1.1 Three Tier Architecture ............................................................................................................ 213 3.2.1.2 Stub und Skeleton ................................................................................................................... 213 3.2.1.3 Interface und Server Technologie am vereinfachten Modell ................................................... 214 3.2.2 Server-Side Components (Selbststudium) .................................................................................. 215 3.2.3 Component Transaction Monitors (Selbststudium)...................................................................... 216 3.3 ARCHITECTURAL OVERVIEW ...................................................................................................................... 217 3.3.1 Beispiel CruiseBean .................................................................................................................... 217 3.3.2 Entity beans ................................................................................................................................. 218 3.3.3 Beispiel TravelAgentBean ........................................................................................................... 218 3.3.4 Session beans ............................................................................................................................. 220 3.3.5 The bean container contract ........................................................................................................ 221 3.3.6 Beispiel Cruise Interface and Class CruiseHome ........................................................................ 221 3.3.7 Classes and Interfaces ................................................................................................................ 221 3.3.8 Implementation des remote interface .......................................................................................... 222 3.3.9 Implementation des home interface ............................................................................................ 223 3.3.10 Beispiel deployment descriptor .................................................................................................... 224 3.3.11 Deployment descriptors and JAR files ......................................................................................... 227 3.4 EXAMPLE CRUISE ..................................................................................................................................... 228 3.5 CONTAINER AND BEAN MANAGED PERSISTENCE OF ENTITY BEANS ............................................................... 228 3.5.1 Container Managed Persistence ................................................................................................. 228 3.5.2 Bean Managed Persistence ........................................................................................................ 228 3.6 STATEFUL AND STATELESS SESSION BEANS ................................................................................................. 228 3.6.1 Stateful session beans ................................................................................................................ 228 3.6.2 Stateless session beans .............................................................................................................. 229 3.7 RESOURCE MANAGEMENT ......................................................................................................................... 229 3.7.1 Problem ....................................................................................................................................... 229 3.7.2 Pooling......................................................................................................................................... 229 3.7.3 Swapping ..................................................................................................................................... 230 3.7.4 The Life Cycle of an Entity Bean ................................................................................................. 230 3.7.4.1 States ...................................................................................................................................... 230 3.7.4.2 Does Not Exist ......................................................................................................................... 231 3.7.4.3 Pooled ..................................................................................................................................... 231 3.7.4.4 Ready ...................................................................................................................................... 231 3.7.4.4.1 Transition from pooled to ready via creation ...................................................................... 231 3.7.4.4.2 Transition from pooled to ready via activation .................................................................... 232 3.7.4.4.3 Transition from ready to pooled via passivation ................................................................. 232 3.7.4.4.4 Transition from ready to pooled via removal ...................................................................... 232 3.7.4.4.5 Anmerkungen ..................................................................................................................... 232 3.7.5 The Life Cycle of a Stateless Session Bean ................................................................................ 233 9 10 3.7.5.1 States ...................................................................................................................................... 233 3.7.5.2 DoesNotExist ........................................................................................................................... 233 3.7.5.3 Method-Ready Pool ................................................................................................................. 233 3.7.5.3.1 Transition to the Method-Ready Pool ................................................................................. 233 3.7.5.3.2 Life in the Method-Ready Pool ........................................................................................... 234 3.7.5.3.3 Transition out of the Method-Ready Pool ........................................................................... 234 3.7.6 The Life Cycle of a Stateful Session Bean .................................................................................. 234 3.7.6.1 The Activation Mechanism ....................................................................................................... 234 3.7.6.2 States ...................................................................................................................................... 235 3.7.6.3 Does Not Exist ......................................................................................................................... 235 3.7.6.4 Method Ready ......................................................................................................................... 235 3.7.6.4.1 Transition to Method Ready ............................................................................................... 235 3.7.6.4.2 Transition to Does Not Exist ............................................................................................... 235 3.7.6.5 Passivated ............................................................................................................................... 235 3.7.6.5.1 Transition to Passivated ..................................................................................................... 235 3.7.6.5.2 Transition to Method Ready ............................................................................................... 236 3.8 PRIMARY SERVICES .................................................................................................................................. 236 3.8.1 Overview ...................................................................................................................................... 236 3.8.2 Concurrency ................................................................................................................................ 236 3.8.3 Persistence .................................................................................................................................. 237 3.8.4 Distributed Objects ...................................................................................................................... 238 3.8.5 Naming ........................................................................................................................................ 239 10 11 Software-Engineering 1 Themenüberblick: Architektur grafischer Benutzungsoberflächen (AWT, Swing, MFC) The Unified Modeling Language (UML) The Unified Software Development Process (RUP) Projekt (MS Project, CVS) Frameworks (EJB) 1 Architektur grafischer Benutzungsoberflächen 1.1 AWT 1.1.1 Beispiel Calculator Das Beispiel „Taschenrechner“ liefert einen Überblick über eine Reihe von Techniken. Diese Techniken werden im Detail erst beim Studium der nachfolgenden Skriptabschnitte verständlich. Fenster AWT SimpleCalculatorAufgabe1 public class Calculator extends Frame implements ActionListener { public static void main( String[] args ) { Frame frame = new Calculator( "Calculator" ); frame.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { Runtime.getRuntime().exit( 0 ); } } ); frame.pack(); frame.setVisible( true ); } private TextField numberField = null; private Panel controlPanel = null; private double leftOperand = 0.0, rightOperand = 0.0, result = 0.0; private boolean operandBufferEmpty = true; private char operand = '='; public Calculator(String title) { super(title); setBackground( Color.red ); setForeground( Color.black ); setLayout( new BorderLayout() ); // control panel controlPanel = new Panel(); controlPanel.setLayout( new GridLayout( 4, 4, 1, 1 ) ); // GridLayout(int rows, int cols, int hgap, int vgap) // Creates a grid layout with the specified number of rows and columns. String[] controlNames = { "7", "8", "9", "/", "4", "5", "6", "*", "1", "2", "3", "-", "0", ".", "=", "+" }; Button button = null; for ( int i = 0; i < controlNames.length; i++ ) { controlPanel.add( button = new Button( controlNames[ i ] ) ); button.addActionListener( this ); } // number field numberField = new TextField( "0", 9 ); // insert control panel and number field in frame add( "South", numberField ); add( "Center", controlPanel ); } 11 12 public void actionPerformed( ActionEvent e ) { // get first character of the pressed buttons title String arg = ( (Button) e.getSource()).getLabel(); char arg0 = arg.charAt( 0 ); // initialize operand buffer double operandBuffer = 0.0; switch( arg0 ) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if ( operandBufferEmpty ) numberField.setText( arg ); else numberField.setText( numberField.getText() + arg ); operandBufferEmpty = false; return; case '.': if ( operandBufferEmpty ) numberField.setText( "0." ); else numberField.setText( numberField.getText() + arg ); operandBufferEmpty = false; return; case '*': case '/': case '+': case '-': try { operandBuffer = Double.valueOf( numberField.getText() ).doubleValue(); } catch ( NumberFormatException nfe ) { return; } operandBufferEmpty = true; leftOperand = operandBuffer; operand = arg0; return; case '=': try { operandBuffer = Double.valueOf( numberField.getText() ).doubleValue(); } catch ( NumberFormatException nfe ) { return; } operandBufferEmpty = true; rightOperand = operandBuffer; switch ( operand ) { case '/': result = leftOperand / rightOperand; break; case '*': result = leftOperand * rightOperand; break; case '-': result = leftOperand - rightOperand; break; case '+': result = leftOperand + rightOperand; break; } numberField.setText( String.valueOf( result ) ); leftOperand = 0.0; rightOperand = 0.0; operand = '='; return; } } } 1.1.2 1.1.2.1 AWT und Swing AWT Im Gegensatz zu den meisten anderen Programmiersprachen wurde Java von Anfang an mit dem Anspruch entwickelt, ein vielseitiges, aber einfach zu bedienendes System für die Gestaltung grafischer Oberflächen zur Verfügung zu stellen. Das Resultat dieser Bemühungen steht seit dem JDK 1.0 als Grafikbibliothek unter dem Namen Abstract Windowing Toolkit (AWT) zur Verfügung. 1.1.2.2 Swing Neben dem AWT gibt es eine zweite Bibliothek für die Gestaltung grafischer Benutzeroberflächen. Sie heißt Swing und ist seit der Version 1.1 fester Bestandteil des Java Development Kit. Da Swing-Anwendungen in ihren Möglichkeiten weit über das hinausgehen, was das AWT bietet, werden heute die meisten GUI-Programme mit Swing geschrieben. Es macht Sinn, sich zunächst mit dem AWT zu beschäftigen. 12 13 Einerseits ist das AWT einfacher zu erlernen, denn es ist weniger umfangreich als Swing. Zum anderen werden viele der AWT-Features auch in Swing benötigt, und das angeeignete Wissen ist nicht verloren. Drittens gibt es auch heute noch Anwendungen für das AWT, etwa bei der AppletProgrammierung, oder wenn die Verwendung von plattformspezifischen Dialogelementen zwingend erforderlich ist. 1.1.2.3 1.1.3 1.1.3.1 Überblick über AWT Components Erstellen eines normalen Fensters oder eines Dialogs Beispiel zum Erzeugen eines Frames SimpleFrameManipulations CreateFrame /* CreateFrame.java */ import java.awt.*; public class CreateFrame { public static void main(String[] args) { // Anlegen eines Fensters Frame frame = new Frame ("Frame entfernen"); Frame.setSize(300,200); // Zuordnen eines Layoutmanagers frame.setLayout(new FlowLayout(FlowLayout.LEFT,20,20); // Einfügen von Dialogelementen frame.add(new frame.add(new frame.add(new frame.add(new frame.add(new Button("Button Button("Button Button("Button Button("Button Button("Button 1")); 2")); 3")); 4")); 5")); // Anzeigen des Fensters frame.pack(); frame.setVisible(true); 13 14 // Pause von 3 Sekunden try { Thread.sleep(3000); } catch (InterruptedException e) { //nichts } // Verstecken des Fensters frame.setVisibe(false); // Freigeben der Ressourcen frame.dispose(); // Beenden des Programmes System.exit(0); } } 1.1.3.2 Aktionen zum Erstellen 1.1.3.2.1 Anlegen eines Dialogfensters Da Java prinzipiell keinen Unterschied zwischen Fenstern zur Ausgabe eines Dialogs und solchen zur Anzeige von Grafiken macht, ist es möglich, ein Dialogfenster wahlweise aus Frame oder Dialog abzuleiten. Die Klasse Dialog erlaubt es, das Verändern der Fenstergröße durch den Anwender zu unterbinden, und bietet die Möglichkeit, den Dialog modal zu machen. Dadurch wird die Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum Schließen des Dialogfensters blockiert. Im Gegensatz zu Frame fehlt jedoch die Möglichkeit, eine Menüleiste zu erzeugen oder dem Fenster ein Icon zuzuordnen. 1.1.3.2.2 Zuordnen eines Layoutmanagers Die Layoutmanager sind in Java für die Anordnung der Dialogelemente im Fenster verantwortlich. Jeder Layoutmanager verfolgt dabei eine eigene Strategie, Elemente zu plazieren und in der Größe so anzupassen, dass sie aus seiner Sicht optimal präsentiert werden. Die Zuordnung eines Layoutmanagers zu einem Fenster wird in der Klasse Container realisiert. Container ist direkt aus Component abgeleitet, und beide zusammen bilden das Gerüst für alle anderen Fensterklassen. Die Klasse Container stellt eine Methode setLayout zur Verfügung, mit der der gewünschte Layoutmanager dem Fenster zugeordnet werden kann: public void setLayout(LayoutManager mgr) Java stellt standardmäßig die fünf Layoutmanager FlowLayout, GridLayout, BorderLayout, CardLayout und GridBagLayout zur Verfügung. Der einfachste Layoutmanager ist FlowLayout, er positioniert die Dialogelemente zeilenweise hintereinander. Passt ein Element nicht mehr in die aktuelle Zeile, so wird es in der nächsten plaziert usw. 1.1.3.2.3 Einfügen von Dialogelementen Das Einfügen von Dialogelementen in das Fenster erfolgt mit der Methode add der Klasse Container: Sollen Komponenten, die bereits an das Fenster übergeben wurden, wieder daraus entfernt werden, so kann dazu die Methode remove verwendet werden. Als Parameter ist dabei das zu löschende Objekt zu übergeben. Container stellt auch Methoden zur Verfügung, um auf die bereits eingefügten Dialogelemente zuzugreifen: Mit getComponentCount kann die Anzahl aller eingefügten Komponenten ermittelt werden. getComponent liefert die Komponente mit dem angegebenen Index, und getComponents gibt ein Array mit allen eingefügten Komponenten zurück. 14 15 1.1.3.2.4 Anzeigen des Dialogfensters Wurden alle Komponenten an den Container übergeben, kann der Dialog formatiert und durch einen Aufruf von setVisible angezeigt werden. Zweckmäßigerweise sollte vorher die Methode pack der Klasse Window aufgerufen werden, um die Größe des Fensters an den zur Darstellung der Dialogelemente erforderlichen Platz anzupassen. 1.1.3.3 Beispiel Anpassen eines Frames SimpleFrameManipulations CustomizeFrame /* CustomizeFrame.java */ import java.awt.*; import java.awt.event.*; public class CustomizeFrame extends Frame { public static void main(String[] args) { CustomizeFrame wnd = new CustomizeFrame(); wnd.setSize(300,200); wnd.setLocation(50,50); wnd.setVisible(true); } public CustomizeFrame() { super(""); assignTitle(); assignIcon(); assignCursor(); assignColors(); assignFont(); addWindowListener(new WindowClosingAdapter(true)); } private void assignTitle() { setTitle("Veränderte Fensterelemente"); } private void assignIcon() { Image img = getToolkit().getImage("testicon.gif"); MediaTracker mt = new MediaTracker(this); mt.addImage(img, 0); try { //Warten, bis das Image vollständig geladen ist, mt.waitForAll(); } catch (InterruptedException e) { //nothing } setIconImage(img); } private void assignCursor() { setCursor(new Cursor(Cursor.WAIT_CURSOR)); } private void assignColors() { setForeground(Color.green); setBackground(Color.blue); } private void assignFont() { setFont(new Font("Serif", Font.PLAIN, 28)); } 15 16 public void paint(Graphics g) { g.drawString("Test in Vordergrundfarbe",10,70); } } 1.1.4 Eventmodell 1.1.4.1 Beispiel ActionEvent public class calculator extends Frame implements ActionListener { public static void main( String[] args ) { Frame frame = new Calculator( "Calculator" ); frame.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent e ) { Runtime.getRuntime().exit( 0 ); } } ); frame.pack(); frame.setVisible( true ); } private TextField numberField = null; public Calculator(String title) { super(title); setBackground( Color.blue ); setLayout(new FlowLayout(FlowLayout.LEFT)); Button button = null; add(button = new Button( "Press" ) ); button.addActionListener(this); numberField = new TextField( "No Comment", 15 ); add(numberField ); } public void actionPerformed(ActionEvent e){ System.out.println("Pressed"); Numberfield.setText("Pressed"); } } 1.1.4.2 Überblick Delegation Event Model oder Delegation Based Event Handling Ereignisgesteuerte Programmierung ist der Hauptbestandteil der Programmierung von Benutzungsoberflächen. Der Benutzer löst durch das Bewegen, Klicken der Maus, Eingabe von Text usw. Ereignisse aus, auf die das Programm reagiert. Applets können das genauso. Die Statusänderungen der Eingabegeräte, also z.B. das Bewegen der Maus, das Drücken der Maustaste oder der Tasten auf der Tastatur, werden dem Java System in Form von Events gemeldet. Das JDK-1.1 führte ein Ereignismodell ein, das auf der Weiterleitung eines Ereignisses von einer Quelle zu einem vorher zu installierenden Lauschobjekt (Listener) beruht. Ist kein Listener für dieses Ereignis aktiviert, wird es auch nicht weitergeleitet. Events sind Objekte, die von einem anderen Objekt ausgesendet werden, der EventSource. Ein Event-Listener teilt der Event-Source sein Interesse an bestimmten Events mit, indem er eine ihrer Registrierungsmethoden aufruft. Er bekommt fortan die entsprechenden Events mitgeteilt bis er sich wieder abmeldet. Die Mitteilung besteht umgekehrt im Aufruf der passenden Methode im Event-Listener, die als Argument eine entsprechende Unterklasse von EventObject erhält. Um sicher zu stellen, dass jeder Event-Listener die benötigten Methoden bereit stellt, müssen die passenden Schnittstellen implementiert werden. 16 17 Die Oberflächenkomponenten erben von der Klasse Component viele der dazu notwendigen Methoden. Überschreibt sie der Programmierer, kann er die Reaktion des Oberflächenkomponenten auf Ereignisse selbst festlegen. 1.1.4.3 Ereignisse 1.1.4.3.1 Diagramm Die Klassenhierarchie enthält eine Wurzelklasse für alle Ereignisse: java.util.EventObject. Damit unterscheiden sich Ereignisse nicht durch bestimmte Identifikatoren, sondern durch ihre Zugehörigkeit zu unterschiedlichen Klassen. Erst wenn verschiedene Ereignisse zu derselben Ereignisklasse gehören, bestimmt ein zusätzlicher Identifikator das Ereignis. Die zu einem Ereignis gehörenden Daten (z.B. die aktuellen Koordinaten der Maus) sind nicht mehr direkt nach außen sichtbar. Stattdessen dienen spezielle Zugriffsmethoden der Abfrage der Werte. Mittels getSource() erhält man das mit den Event verknüpfte Objekt, mittels getID() die den Event kennzeichnende Event-ID. Das Ereignismodell unterscheidet zwischen primitiven Ereignissen (low-level event) und Ereignissen zur Interaktion verschiedener Komponenten (semantic event). Zu Komponenten zählen sowohl AWT-Komponenten als auch andere Einheiten (wie z.B. Timer), die in ein Ereignismodell passen. 1.1.4.3.2 Primitive Ereignisse Als primitive Ereignisse implementiert das AWT im Paket java.awt.event: ComponentEvent (Größen- und Positionsänderung), ContainerEvent (Hinzufügen oder Löschen einer Komponente aus dem Container), FocusEvent (Abgabe und Zuweisung des Eingabefokus), WindowEvent (Änderung eines Fensters), 17 18 InputEvent mit den Subklassen KeyEvent (Tastatur-Eingaben) und MouseEvent (Mausbewegungen und Maustasten). 1.1.4.3.3 Ereignisse zur Interaktion verschiedener Komponenten ActionEvent(Aufforderung zur Ausführung einer Aktion), AdjustmentEvent (Wertänderung, z.B. bei Scrollbar), ItemEvent (Änderung der Einstellung einer Auswahl, z.B. bei Choice oder Checkbox) und TextEvent (Änderung eines Textes). 1.1.4.4 Listener 1.1.4.4.1 Konzept Lauschobjekte (Listener) empfangen und verarbeiten Ereignisse. Um diese Funktion ausführen zu können, muss der Listener eine spezielle auf die jeweiligen Ereignisse angepasste Schnittstelle implementieren. Die Schnittstelle bestimmt, welche Methoden im Listener implementiert werden müssen, um das Ereignis verarbeiten zu können. Im Allgemeinen enthält die Schnittstelle Bearbeitungsmethoden für jedes Ereignis, das die zu bedienende Ereignisklasse repräsentiert. Erst die Installation eines entsprechenden Listeners führt auch zur Weiterleitung des Ereignisses. Hierin liegt ein entscheidender Performanzgewinn. Insbesondere hochfrequente Ereignisse, die zum Beispiel beim Bewegen der Maus entstehen, werden nur geliefert, wenn die Komponente sie auch wirklich benötigt. 1.1.4.4.2 Listenerdiagramm 1.1.4.4.3 Listener-Schnittstellen Für primitive Ereignisse enthält das AWT in java.awt.event die Listener-Schnittstellen ComponentListener, ContainerListener, MouseMotionListener, MouseListener, FocusListener, 18 19 KeyListener und WindowListener, für die Ereignisse zur Interaktion ActionListener, AdjustmentListener, ItemListener und TextListener. Einige dieser Schnittstellen enthalten mehrere Methoden für verschiedene Ereignisse, MouseListener z.B. mouseClicked(), mouseEntered(), mouseExited(), mousePressed() und mouseReleased(). Diese Wahl wurde getroffen, um nicht für jedes Ereignis eine Listener-Klasse zu benötigen. 1.1.4.5 Ereignisquellen 1.1.4.5.1 Konzept Eine Ereignisquelle liefert Ereignisse an Listener. Das können sowohl ein als auch mehrere Listener sein, je nachdem, wie sie installiert wurden. Ist kein Listener zu einem Ereignis installiert, so wird es nicht geliefert. Sendet die Quelle ein Ereignis an mehrere Listener, so darf der Programmierer über die Reihenfolge der bedienten Listener keine Annahme treffen. Er sollte also kein Programm schreiben, das von einer bestimmten Reihenfolge ausgeht. Jeder Listener erhält dabei eine Kopie des ursprünglichen Ereignisses, so dass eventuelle Änderungen des Ereignisses in einem Listener keine Wirkung auf einen anderen Listener haben. 1.1.4.5.2 Ereignisquellen Quellen von primitiven Ereignissen sind die GUI-Komponenten des AWT: Component ( ComponentEvent, FocusEvent, KeyEvent, MouseEvent, MouseMotionEvent), Dialog ( WindowEvent) und Frame ( WindowEvent) Quellen von Ereignissen zur Interaktion von Komponenten sind Button ( ActionEvent), MenuItem ( ActionEvent), List ( ActionEvent, ItemEvent), Choice ( ItemEvent), Checkbox ( ItemEvent), CheckboxMenuItem ( ItemEvent) und Scrollbar ( AdjustmentEvent). Mit einer Klasse sind natürlich auch die von ihr abgeleiteten Klassen Quellen der entsprechenden Ereignisse. 1.1.4.6 Aktivierung von Lauschobjekten Jede Quelle besitzt für die von ihr gelieferten Ereignisse Methoden, Listener für diese Ereignisse zu aktivieren. Will der Programmierer mehrere Listener je Ereignis aktivieren, 19 20 verwendet er die addXXXListener()-Methoden, anderenfalls die setXXXListener()Methoden. XXX steht dabei für das jeweilige Ereignis. 1.1.4.7 Beispiel MouseEventsDemoEventTechniken 1.1.4.7.1 Implementation Listener durch Frame /* MouseEvents.java */ import java.awt.*; import java.awt.event.*; public class MouseEvents extends Frame implements MouseListener { Canvas c1 = null; Canvas c2 = null; public static void main(String[] args) { MouseEvents wnd = new MouseEvents(); wnd.setVisible(true); } public MouseEvents() { super("Mausklicks"); setSize(500,400); setLayout(new FlowLayout()); setLocation(200,100); int fx = getSize().height / 2; int fy = getSize().width / 2; c1 = new Canvas(); c1.setBackground(Color.blue); c1.setSize(fx, fy); add(c1); c2 = new Canvas(); c2.setBackground(Color.red); c2.setSize(fx, fy); add(c2); pack(); addWindowListener(new WindowClosingAdapter(true)); c1.addMouseListener(this); } // Variante Frame implementiert MouseListener selbst public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mousePressed(MouseEvent event) { Graphics g = c1.getGraphics(); int x = event.getX(); int y = event.getY(); g.drawOval(x-10,y-10,20,20); } } 20 21 1.1.4.7.2 Adapter /* MouseEvents.java */ import java.awt.*; import java.awt.event.*; public class MouseEvents extends Frame { Canvas c1 = null; Canvas c2 = null; public static void main(String[] args) { MouseEvents wnd = new MouseEvents(); wnd.setVisible(true); } public MouseEvents() { super("Mausklicks"); setSize(500,400); setLayout(new FlowLayout()); setLocation(200,100); int fx = getSize().height / 2; int fy = getSize().width / 2; c1 = new Canvas(); c1.setBackground(Color.blue); c1.setSize(fx, fy); add(c1); c2 = new Canvas(); c2.setBackground(Color.red); c2.setSize(fx, fy); add(c2); pack(); addWindowListener(new WindowClosingAdapter(true)); c1.addMouseListener(new MyMouseListener()); } // Variante Frame erzeugt Objekt einer Klasse, die von MouseAdapter abgeleitet ist class MyMouseListener extend MouseAdapter { public void mousePressed(MouseEvent event) { Graphics g = c1.getGraphics(); int x = event.getX(); int y = event.getY(); g.drawOval(x-10,y-10,20,20); } } } 1.1.4.7.3 Anonyme Klasse /* MouseEvents.java */ import java.awt.*; import java.awt.event.*; public class MouseEvents extends Frame { Canvas c1 = null; Canvas c2 = null; public static void main(String[] args) { MouseEvents wnd = new MouseEvents(); wnd.setVisible(true); } public MouseEvents() { super("Mausklicks"); setSize(500,400); 21 22 setLayout(new FlowLayout()); setLocation(200,100); int fx = getSize().height / 2; int fy = getSize().width / 2; c1 = new Canvas(); c1.setBackground(Color.blue); c1.setSize(fx, fy); add(c1); c2 = new Canvas(); c2.setBackground(Color.red); c2.setSize(fx, fy); add(c2); pack(); addWindowListener(new WindowClosingAdapter(true)); // Variante Frame erzeugt Objekt einer Klasse, die von MouseAdapter abgeleitet ist c1.addMouseListener(new MouseAdapter(){ public void mousePressed(MouseEvent e) { Graphics g = c1.getGraphics(); g.drawOval(e.getX(), e.getY(), 20 , 20); } } ); } } 1.1.4.8 Adapter Klassen Wenn z.B. die MouseListener Klasse keine Schnittstelle sondern eine beerbbare normale Klasse wäre und Java Mehrfachvererbung erlauben würde, könnte man in der MouseListener Klasse für jedes Ereignis eine Standardreaktion implementieren. Eine spezifische von MouseListener abgeleitete Klasse müsste nur diejenige Handlerfunktion überschreiben, die neu definiert werden soll. Da es sich jedoch bei der Klasse MouseListener um eine reine Schnittstelle handelt ist keine der Handlerfunktionen implementiert. Daher müsste die von MouseListener abgeleitete Klasse wirklich alle Handlerfunktionen ausfüllen. Es wäre jedoch für den Programmierer sehr umständlich, jede der enthaltenen Methoden ausfüllen zu müssen, auch wenn er nur ein einziges Ereignis (z.B. mousePressed()) behandeln will. Für jede der oben genannten Schnittstellen enthält das AWT deshalb eine sogenannte Adapter-Klasse, eine Klasse, die genau diese Schnittstelle implementiert. Diese Klassen kann der Programmierer erweitern und muss dann nur noch die für das gewünschte Ereignis zuständige Methode überschreiben. Die vorhandenen Adapter sind : ComponentAdapter, ContainerAdapter, FocusAdapter, KeyAdapter, MouseAdapter, MouseMotionAdapter und WindowAdapter. Da die Schnittstellen für die Listener der Interaktionsereignisse jeweils nur eine Methode enthalten, existieren für sie keine Adapter. 1.1.4.9 Adapter als innere Klassen Das Einerben eines Adapters funktioniert wegen der verbotenen Mehrfachvererbung nicht, wenn die Klasse bereits von einer anderen Klasse abgeleitet wurde. In diesem Falle wird der Adapter als innere Klasse realisert. 22 23 1.1.4.10 Beispiele 1.1.4.10.1 Beispiel Schließen eines Frames mit Escape Fenster AWT Events SchliessenMitEscape 1.1.4.10.1.1 Implementation Keylistener /* FrameManip.java */ import java.awt.*; import java.awt.event.*; public class FrameManip extends Frame implements KeyListener { public static void main(String[] args) { FrameManip wnd = new FrameManip(); } public FrameManip() { super("Nachrichtentransfer"); setBackground(Color.lightGray); setSize(300,200); setLocation(200,100); setVisible(true); addKeyListener(this); } public void paint(Graphics g) { g.setFont(new Font("Serif",Font.PLAIN,18)); g.drawString("Zum Beenden bitte ESC drücken...",10,50); } public void keyPressed(KeyEvent event) { if (event.getKeyCode() == KeyEvent.VK_ESCAPE) { setVisible(false); dispose(); System.exit(0); } } public void keyReleased(KeyEvent event) { } public void keyTyped(KeyEvent event) { } } 1.1.4.10.1.2 Innerere Klasse und Adapter import java.awt.*; import java.awt.event.*; public class FrameManip extends Frame { public static void main(String[] args) { FrameManip wnd = new FrameManip(); } 23 24 public FrameManip() { super("Nachrichtentransfer"); setBackground(Color.lightGray); setSize(300,200); setLocation(200,100); setVisible(true); addKeyListener(new MyKeyListener()); } public void paint(Graphics g) { g.setFont(new Font("Serif",Font.PLAIN,18)); g.drawString("Zum Beenden bitte ESC drücken...",10,50); } class MyKeyListener extends KeyAdapter { public void keyPressed(KeyEvent event) { if (event.getKeyCode() == KeyEvent.VK_ESCAPE) { setVisible(false); dispose(); System.exit(0); } } } } 1.1.4.10.1.3 Anonyme Klasse import java.awt.*; import java.awt.event.*; public class Listing2804 extends Frame { public static void main(String[] args) { Listing2804 wnd = new Listing2804(); } public Listing2804() { super("Nachrichtentransfer"); setBackground(Color.lightGray); setSize(300,200); setLocation(200,100); setVisible(true); addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent event) { if (event.getKeyCode() == KeyEvent.VK_ESCAPE) { setVisible(false); dispose(); System.exit(0); } } } ); } public void paint(Graphics g) { g.setFont(new Font("Serif",Font.PLAIN,18)); g.drawString("Zum Beenden bitte ESC drücken...",10,50); } } 1.1.4.10.1.4 Überlagern der Event-Handler in den Komponenten Jede Ereignisquelle besitzt eine Reihe von Methoden, die für das Aufbereiten und Verteilen der Nachrichten zuständig sind. Soll eine Nachricht weitergereicht werden, so wird dazu 24 25 zunächst innerhalb der Nachrichtenquelle die Methode processEvent aufgerufen. Diese verteilt die Nachricht anhand ihres Typs an spezialisierte Methoden, deren Name sich nach dem Typ der zugehörigen Ereignisklasse richtet. So ist beispielsweise die Methode processActionEvent für das Handling von Action-Events und processMouseEvent für das Handling von Mouse-Events zuständig: protected void processEvent(AWTEvent e) protected void processComponentEvent(ComponentEvent e) protected void processFocusEvent(FocusEvent e) Beide Methodenarten können in einer abgeleiteten Klasse überlagert werden, um die zugehörigen Ereignisempfänger zu implementieren. Wichtig ist dabei, dass in der abgeleiteten Klasse die gleichnamige Methode der Basisklasse aufgerufen wird, um das Standardverhalten sicherzustellen. Wichtig ist weiterhin, dass sowohl processEvent als auch processActionEvent usw. nur aufgerufen werden, wenn der entsprechende Ereignistyp für diese Ereignisquelle aktiviert wurde. Dies passiert in folgenden Fällen: Wenn ein passender Ereignisempfänger über die zugehörige addEventListener-Methode registriert wurde. Wenn der Ereignistyp explizit durch Aufruf der Methode enableEvents aktiviert wurde. Die Methode enableEvents erwartet als Argument eine Maske, die durch eine bitweise Oder-Verknüpfung der passenden Maskenkonstanten aus der Klasse AWTEvent zusammengesetzt werden kann: protected final void enableEvents(long eventsToEnable) java.awt.Component Die verfügbaren Masken sind analog zu den Ereignistypen benannt und heißen ACTION_EVENT_MASK, ADJUSTMENT_EVENT_MASK, COMPONENT_EVENT_MASK usw. Das folgende Beispiel überlagert die Methode processKeyEvent in der Klasse Frame (die sie aus Component geerbt hat). Durch Aufruf von enableEvents wird die Weiterleitung der Tastaturereignisse aktiviert, und das Programm zeigt dasselbe Verhalten wie die vorigen Programme. import java.awt.*; import java.awt.event.*; public class FrameManip extends Frame { public static void main(String[] args) { FrameManip wnd = new FrameManip(); } public FrameManip() { super("Nachrichtentransfer"); setBackground(Color.lightGray); setSize(300,200); setLocation(200,100); setVisible(true); enableEvents(AWTEvent.KEY_EVENT_MASK); } public void paint(Graphics g) { g.setFont(new Font("Serif",Font.PLAIN,18)); g.drawString("Zum Beenden bitte ESC drücken...",10,50); } public void processKeyEvent(KeyEvent event) 25 26 { if (event.getID() == KeyEvent.KEY_PRESSED) { if (event.getKeyCode() == KeyEvent.VK_ESCAPE) { setVisible(false); dispose(); System.exit(0); } } super.processKeyEvent(event); } } Diese Art der Ereignisbehandlung ist nur sinnvoll, wenn Fensterklassen oder Dialogelemente überlagert werden und ihr Aussehen oder Verhalten signifikant verändert wird. Alternativ könnte natürlich auch in diesem Fall ein EventListener implementiert und die entsprechenden Methoden im Konstruktor der abgeleiteten Klasse registriert werden. Das hier vorgestellte Verfahren umgeht das Delegation Event Model vollständig und hat damit die gleichen inhärenten Nachteile wie das Event-Handling des alten JDK. Die Dokumentation zum JDK empfiehlt daher ausdrücklich, für alle »normalen« Anwendungsfälle das Delegation Event Model zu verwenden. 1.1.4.10.2 Key Events Unter Windows werden alle Tastatureingaben an die fokussierte Komponente gesendet. Ein Empfänger für Key-Events muss das Interface KeyListener implementieren und bekommt Events des Typs KeyEvent übergeben. KeyEvent erweitert die Klasse InputEvent, die ihrerseits aus ComponentEvent abgeleitet ist, und stellt neben getID und getSource eine ganze Reihe von Methoden zur Verfügung, mit denen die Erkennung und Bearbeitung der Tastencodes vereinfacht wird. Zeichentasten sind solche Tasten, mit denen Buchstaben, Ziffern oder sonstige gültige Unicode-Zeichen eingegeben werden, wie z.B. [a], [A], [B], [1], [2], [%], [+], aber auch [ESC], [LEER] oder [TAB]. Zu den Funktionstasten gehören beispielsweise [F1], [F2], [POS1] oder [CURSORLINKS], aber auch die Umschalttasten [STRG], [ALT] und [UMSCHALT]. Die Methode keyTyped wird immer dann aufgerufen, wenn eine Zeichentaste gedrückt wurde. Beim Drücken einer Funktionstaste wird sie dagegen nicht aufgerufen. Im Gegensatz dazu wird keyPressed bei jedem Tastendruck aufgerufen, unabhängig davon, ob es sich um eine Zeichentaste oder eine Funktionstaste handelt. Beide Methoden erhalten auch Tastatur-Repeats, werden also bei längerem Festhalten einer Taste wiederholt aufgerufen. Die Methode keyReleased wird aufgerufen, wenn eine gedrückte Taste losgelassen wurde, unabhängig davon, ob es sich um eine Zeichen- oder Funktionstaste handelt. getKeyChar liefert das Zeichen, das der gedrückten Zeichentaste entspricht, also ein 'a', wenn die Taste [A] gedrückt wurde, und ein 'A', wenn die Tastenkombination [UMSCHALT]+[A] gedrückt wurde. getKeyCode liefert dagegen virtuelle Tastencodes, die in KeyEvent als symbolische Konstanten definiert wurden. Hier wird beim Drücken der Taste [A] immer der Code VK_A geliefert, unabhängig davon, ob [UMSCHALT] gedrückt wurde oder nicht. Die folgende Tabelle gibt eine Übersicht der wichtigsten virtuellen Keycodes der Klasse KeyEvent. Symbolischer Name VK_0 ... VK_9 VK_A ... VK_Z VK_ENTER VK_SPACE Bedeutung [0] ... [9] [A] ... [Z] [ENTER] [LEER] 26 27 VK_TAB VK_ESCAPE VK_BACK_SPACE VK_F1 ... VK_F12 VK_HOME, VK_END VK_INSERT, VK_DELETE VK_PAGE_UP, VK_PAGE_DOWN VK_DOWN, VK_UP VK_LEFT, VK_RIGHT [TAB] [ESC] [RÜCK] Die Funktionstasten [F1] ... [F12] [HOME], [END] [EINFG], [ENTF] [BILDHOCH], [BILDRUNTER] [CURSORHOCH], [CURSORRUNTER] [CURSORLINKS], [CURSORRECHTS] Am einfachsten ist es, innerhalb von keyTyped mit getKeyChar die Zeichentasten abzufragen. Dabei liefert getKeyChar stets den ASCII-Code der gedrückten Zeichentaste, Funktionstasten werden nicht übertragen. Der Rückgabewert von getKeyCode ist in diesem Fall immer KeyEvent.VK_UNDEFINED. Sollen dagegen auch Funktionstasten abgefragt werden, muß die Methode keyPressed überlagert werden. Hier ist etwas Vorsicht geboten, denn es wird auf alle Tastendrücke reagiert, und sowohl getKeyCode als auch getKeyChar liefern Werte zurück. Die Unterscheidung von Zeichen- und Funktionstasten kann in diesem Fall mit Hilfe von getKeyChar vorgenommen werden, deren Rückgabewert die Konstante KeyEvent.CHAR_UNDEFINED ist, wenn eine Funktionstaste gedrückt wurde. Die is-Methoden sind bereits bekannt, mit ihnen können die Umschalttasten abgefragt werden. Das ist beispielsweise sinnvoll, um bei einer Funktionstaste herauszufinden, ob sie mit gedrückter Umschalttaste ausgelöst wurde oder nicht. keyTyped keyPressed getKeyCode getKeyChar Zeichentaste: VK_UNDEFINED Funktionstaste: -Zeichentaste: VK_... Funktionstaste: VK_... Zeichentaste: Taste als char Funktionstaste: -Zeichentaste: Taste als char Funktionstaste: CHAR_UNDEFINED Das Beispiel demonstriert die Abfrage der Tastaturereignisse. Es implementiert keyPressed, um die Funktionstasten [F1] bis [F3] und den Status der Umschalttasten abzufragen. Jeder Tastendruck wird in einen String übersetzt, in msg1 gespeichert und durch Aufruf von repaint auf dem Bildschirm angezeigt. Nach dem Loslassen der Taste wird die Anzeige wieder vom Bildschirm entfernt. Weiterhin wurde keyTyped überlagert, um die Zeichentasten abzufragen. Jeder Tastendruck wird in msg2 gespeichert und ebenfalls auf dem Bildschirm angezeigt. Im Gegensatz zu den Funktionstasten bleibt die Ausgabe auch erhalten, wenn die Taste losgelassen wird. Bei jedem weiteren Tastendruck wird sie um ein Zeichen ergänzt. Zusätzlich werden die einzelnen Ereignisse auf der Konsole dokumentiert. /* KeyEvents.java */ import java.awt.*; import java.awt.event.*; public class KeyEvents extends Frame implements KeyListener { String msg1 = ""; String msg2 = ""; public static void main(String[] args) { KeyEvents wnd = new KeyEvents(); } public KeyEvents() 27 28 { super("Tastaturereignisse"); addKeyListener(this); addWindowListener(new WindowClosingAdapter(true)); setBackground(Color.lightGray); setSize(300,200); setLocation(200,100); setVisible(true); } public void paint(Graphics g) { if (msg1.length() > 0) { draw3DRect(g,20,50,250,30); g.setColor(Color.black); g.drawString(msg1,30,70); } if (msg2.length() > 0) { draw3DRect(g,20,100,250,30); g.setColor(Color.black); g.drawString(msg2,30,120); } } void draw3DRect(Graphics g,int x,int y,int width,int height) { g.setColor(Color.darkGray); g.drawLine(x,y,x,y+height); g.drawLine(x,y,x+width,y); g.setColor(Color.white); g.drawLine(x+width,y+height,x,y+height); g.drawLine(x+width,y+height,x+width,y); } public void keyPressed(KeyEvent event) { msg1 = ""; System.out.println( "key pressed: " + "key char = " + event.getKeyChar() + " " + "key code = " + event.getKeyCode() ); if (event.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { int key = event.getKeyCode(); //Funktionstaste abfragen if (key == KeyEvent.VK_F1) { msg1 = "F1"; } else if (key == KeyEvent.VK_F2) { msg1 = "F2"; } else if (key == KeyEvent.VK_F3) { msg1 = "F3"; } //Modifier abfragen if (msg1.length() > 0) { if (event.isAltDown()) { msg1 = "ALT + " + msg1; } if (event.isControlDown()) { msg1 = "STRG + " + msg1; } if (event.isShiftDown()) { msg1 = "UMSCHALT + " + msg1; } } } repaint(); } public void keyReleased(KeyEvent event) { System.out.println("key released"); msg1 = ""; repaint(); } public void keyTyped(KeyEvent event) { char key = event.getKeyChar(); System.out.println("key typed: " + key); if (key == KeyEvent.VK_BACK_SPACE) { if (msg2.length() > 0) { msg2 = msg2.substring(0,msg2.length() - 1); } 28 29 } else if (key >= KeyEvent.VK_SPACE) { if (msg2.length() < 40) { msg2 += event.getKeyChar(); } } repaint(); } } 1.1.4.10.3 Mouse Events nur elektronisch 1.1.4.10.4 Component Events nur elektronisch 1.1.4.10.5 Focus Events nur elektronisch 1.1.5 1.1.5.1 Layoutmanager Beispiel Layout /* Layout.java */ import java.awt.*; import java.awt.event.*; public class Layout extends Frame { public static void main(String[] args) { Layout wnd = new Layout(); wnd.setVisible(true); } public Layout() { super("Test Layout"); addWindowListener(new WindowClosingAdapter(true)); setLayout(new GridLayout(5,1)); // Panel fuer FlowLayout Panel fp = new Panel(); //Layout setzen und Komponenten hinzufügen fp.setLayout(new FlowLayout(FlowLayout.LEFT,20,20)); fp.add(new Button("Button 1")); fp.add(new Button("Button 2")); fp.add(new Button("Button 3")); fp.add(new Button("Button 4")); fp.add(new Button("Button 5")); add(fp); // Panel fuer GridLayout Panel gp = new Panel(); //Layout setzen und Komponenten hinzufügen gp.setLayout(new GridLayout(4,2)); gp.add(new Button("Button 1")); gp.add(new Button("Button 2")); gp.add(new Button("Button 3")); gp.add(new Button("Button 4")); gp.add(new Button("Button 5")); gp.add(new Button("Button 6")); gp.add(new Button("Button 7")); add(gp); // Panel fuer BorderLayout 29 30 Panel bp = new Panel(); //Layout setzen und Komponenten hinzufügen bp.setLayout(new BorderLayout()); bp.add("North", new Button("Button 1")); bp.add("South", new Button("Button 2")); bp.add("West", new Button("Button 3")); bp.add("East", new Button("Button 4")); bp.add("Center", new Button("Button 5")); add(bp); // Geschachtelte Layoutmanager //Panel 1 Panel panel1 = new Panel(); panel1.setLayout(new GridLayout(3,2)); panel1.add(new Button("Button1")); panel1.add(new Button("Button2")); panel1.add(new Button("Button3")); panel1.add(new Button("Button4")); panel1.add(new Button("Button5")); //Panel 2 Panel panel2 = new Panel(); panel2.setLayout(new BorderLayout()); panel2.add("North", new Button("Button4")); panel2.add("South", new Button("Button5")); panel2.add("West", new Button("Button6")); panel2.add("East", new Button("Button7")); panel2.add("Center", panel1); add(panel2); pack(); } } 1.1.5.2 Konzept In vielen grafischen Oberflächen wird die Anordnung der Elemente eines Dialoges durch Angabe absoluter Koordinaten vorgenommen. Dabei wird für jede Komponente manuell oder mit Hilfe eines Ressourcen-Editors pixelgenau festgelegt, an welcher Stelle im Dialog sie zu erscheinen hat. Da Java-Programme auf vielen unterschiedlichen Plattformen mit unterschiedlichen Ausgabegeräten laufen sollen, war eine solche Vorgehensweise für die Designer des AWT nicht akzeptabel. Sie wählten statt dessen den Umweg über einen Layoutmanager, der für die Anordnung der Dialogelemente verantwortlich ist. Um einen Layoutmanager verwenden zu können, wird dieser dem Fenster vor der Übergabe der Dialogelemente mit der Methode setLayout zugeordnet. Er ordnet dann die per add übergebenen Elemente auf dem Fenster an. Jeder Layoutmanager implementiert seine eigene Logik bezüglich der optimalen Anordnung der Komponenten: Neben den gestalterischen Fähigkeiten eines Layoutmanagers bestimmt in der Regel die Reihenfolge der Aufrufe der add-Methode des Fensters die tatsächliche Anordnung der Komponenten auf dem Bildschirm. Wenn nicht - wie es z.B. beim BorderLayout möglich ist zusätzliche Positionierungsinformationen an das Fenster übergeben werden, ordnet der jeweilige Layoutmanager die Komponenten in der Reihenfolge ihres Eintreffens an. Eines der Schlüsselkonzepte zur Realisierung komplexer, portabler Dialoge ist die Fähigkeit, Layoutmanager schachteln zu können. Dazu wird an der Stelle, die ein Sublayout erhalten soll, einfach ein Objekt der Klasse Panel eingefügt, das einen eigenen Layoutmanager erhält. Dieses Panel kann mit Dialogelementen bestückt werden, die entsprechend dem zugeordneten Unterlayout formatiert werden. 1.1.5.3 FlowLayout Die Klasse FlowLayout stellt den einfachsten Layoutmanager dar. Alle Elemente werden so lange nacheinander in einer Zeile angeordnet, bis kein Platz mehr vorhanden ist und in der nächsten Zeile fortgefahren wird. 30 31 1.1.5.4 GridLayout Ein GridLayout bietet eine größere Kontrolle über die Anordnung der Elemente als ein FlowLayout. Hier werden die Komponenten nicht einfach nacheinander auf dem Bildschirm positioniert, sondern innerhalb eines rechteckigen Gitters angeordnet, dessen Elemente eine feste Größe haben. Das Programm übergibt dazu beim Aufruf des Konstruktors zwei Parameter, rows und columns, mit denen die vertikale und horizontale Anzahl an Elementen festgelegt wird: Beim Aufruf von add werden die Komponenten dann nacheinander in die einzelnen Zellen der Gittermatrix gelegt. Analog zum FlowLayout wird dabei zunächst die erste Zeile von links nach rechts gefüllt, dann die zweite usw. Die größere Kontrolle des Programms über das Layout besteht nun darin, dass der Umbruch in die nächste Zeile genau vorhergesagt werden kann. Anders als beim FlowLayout erfolgt dieser nicht erst dann, wenn keine weiteren Elemente in die Zeile passen, sondern wenn dort so viele Elemente plaziert wurden, wie das Programm vorgegeben hat. Die Größe der Komponenten Wenn wir die Größe des Fensters nicht durch einen Aufruf von pack, sondern manuell festgelegen, wird ein wichtiger Unterschied zwischen den beiden bisher vorgestellten Layoutmanagern deutlich. Der Unterschied besteht darin, dass ein FlowLayout die gewünschte Größe eines Dialogelements verwendet, um seine Ausmaße zu bestimmen. Das GridLayout dagegen ignoriert die gewünschte Größe und dimensioniert die Dialogelemente fest in der Größe eines Gitterelements. Ein LayoutManager hat also offensichtlich die Freiheit zu entscheiden, ob und in welcher Weise er die Größen von Fenster und Dialogelementen den aktuellen Erfordernissen anpasst. 1.1.5.5 BorderLayout Das BorderLayout verfolgt einen anderen Ansatz als die beiden vorigen Layoutmanager, denn die Positionierung der Komponenten wird nicht mehr primär durch die Reihenfolge der Aufrufe von add bestimmt. Statt dessen teilt das BorderLayout den Bildschirm in fünf Bereiche auf, und zwar in die vier Ränder und das Zentrum. Durch Angabe eines Himmelsrichtungs-Strings wird beim Aufruf von add angegeben, auf welchem dieser Bereiche die Komponente plaziert werden soll: "South": unterer Rand "North": oberer Rand "East": rechter Rand "West": linker Rand "Center": Mitte Bezüglich der Skalierung der Komponenten verfolgt BorderLayout einen Mittelweg zwischen FlowLayout und GridLayout. Während FlowLayout die Komponenten immer in ihrer gewünschten Größe belässt und GridLayout sie immer skaliert, ist dies bei BorderLayout von verschiedenen Faktoren abhängig: Nord- und Südelement behalten ihre gewünschte Höhe, werden aber auf die volle Fensterbreite skaliert. Ost- und Westelement behalten ihre gewünschte Breite, werden aber in der Höhe so skaliert, dass sie genau zwischen Nord- und Südelement passen. Das Mittelelement wird in der Höhe und Breite so angepasst, dass es den verbleibenden freien Raum einnimmt. Auch beim BorderLayout kann die Größe der Lücken zwischen den Elementen an den Konstruktor übergeben werden. 31 32 1.1.5.6 CardLayout Das CardLayout ist in der Lage, mehrere Unterdialoge in einem Fenster unterzubringen und jeweils einen davon auf Anforderung des Programms anzuzeigen. 1.1.5.7 GridBagLayout Das GridBagLayout ist ein komplexer Layoutmanager, der die Fähigkeiten von GridLayout erweitert und es ermöglicht, mit Hilfe von Bedingungsobjekten sehr komplexe Layouts zu erzeugen. 1.1.5.8 1.1.6 Null-Layout Sollte auch die GridBagLayout Variante nicht genau genug sein, so bietet sich durch Verwendung eines Null-Layouts die Möglichkeit an, Komponenten durch Vorgabe fester Koordinaten zu plazieren. Modale Dialoge 1.1.6.1 Beispiel Dialoge /* Dialoge.java */ import java.awt.*; import java.awt.event.*; class YesNoDialog extends Dialog implements ActionListener { boolean result; public YesNoDialog(Frame owner, String msg) { super(owner, "Ja-/Nein-Auswahl", true); //mit true wartet auf antwort //Fenster setBackground(Color.lightGray); setLayout(new BorderLayout()); setResizable(false); //Hinweis im Text beachten Point fatherLocation = owner.getLocation(); setLocation(fatherLocation.x + 30, fatherLocation.y + 30); //Message add("Center", new Label(msg)); //Buttons Panel panel = new Panel(); panel.setLayout(new FlowLayout(FlowLayout.CENTER)); Button button = new Button("Ja"); button.addActionListener(this); panel.add(button); button = new Button("Nein"); button.addActionListener(this); panel.add(button); add("South", panel); pack(); } public void actionPerformed(ActionEvent event) { result = event.getActionCommand().equals("Ja"); setVisible(false); dispose(); } public boolean getResult() { return result; } } public class Dialoge extends Frame implements ActionListener { 32 33 public static void main(String[] args) { Dialoge wnd = new Dialoge(); wnd.setVisible(true); } public Dialoge() { super("Modale Dialoge"); setLayout(new FlowLayout()); setBackground(Color.lightGray); Button button = new Button("Ende"); button.addActionListener(this); add(button); setLocation(100,100); setSize(300,200); setVisible(true); } public void actionPerformed(ActionEvent event) { String cmd = event.getActionCommand(); if (cmd.equals("Ende")) { YesNoDialog dlg; dlg = new YesNoDialog(this, "Wollen Sie das Programm wirklich beenden?"); dlg.setVisible(true); //Auf das Schließen des Dialogs warten... if (dlg.getResult()) { setVisible(false); dispose(); System.exit(0); } } } } 1.1.6.2 Warten auf die Eingabe Modale Dialoge sind solche, die alle Benutzereingaben des Programmes beanspruchen und andere Fenster erst dann wieder zum Zuge kommen lassen, wenn das Dialogfenster geschlossen wird. Eine wichtige Eigenschaft modaler Dialoge ist es, dass im Programm der Aufruf zur Anzeige des Dialogs so lange blockiert, bis der Dialog beendet ist. Auf diese Weise kann an einer bestimmten Stelle im Programm auf eine Eingabe gewartet werden und erst dann mit der Bearbeitung fortgefahren werden, wenn die Eingabe erfolgt ist. Ein modaler Dialog muss immer aus der Klasse Dialog abgeleitet werden. Nur sie bietet die Möglichkeit, an den Konstruktor einen booleschen Wert zu übergeben, der festlegt, dass die übrigen Fenster der Anwendung während der Anzeige des Dialogs suspendiert werden. Als erstes Argument des Konstruktors muss in jedem Fall ein Frame- oder Dialog-Objekt als Vaterfenster übergeben werden. Mit title kann der Inhalt der Titelzeile vorgegeben werden, und der Parameter modal entscheidet, ob der Dialog modal dargestellt wird oder nicht. 1.1.6.3 Beispiel VariableDialoge nur elektronisch 1.2 Swing 1.2.1 Beispiel Geburtsmonat Fenster Swing Geburtsmonat /* Geburtsmonat.java */ import java.awt.event.*; //import javax.swing.*; // Funktioniert nicht mit VisualCafe 33 34 import com.sun.java.swing.*; public class Geburtsmonat extends JFrame implements ActionListener { private static final String[] MONTHS = { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" }; public Geburtsmonat() { super("Swing-Programm"); //Panel zur Namenseingabe hinzufügen JPanel namePanel = new JPanel(); JLabel label = new JLabel( "Name:", new ImageIcon("testicon.gif"), SwingConstants.LEFT ); namePanel.add(label); JTextField tf = new JTextField(30); tf.setToolTipText("Geben Sie ihren Namen ein"); namePanel.add(tf); namePanel.setBorder(BorderFactory.createEtchedBorder()); getContentPane().add("North", namePanel); //Monatsliste hinzufügen JList list = new JList(MONTHS); list.setToolTipText("Wählen Sie ihren Geburtsmonat aus"); getContentPane().add("Center", new JScrollPane(list)); //Panel mit den Buttons hinzufügen JPanel buttonPanel = new JPanel(); JButton button1 = new JButton("Metal"); button1.addActionListener(this); button1.setToolTipText("Metal-Look-and-Feel aktivieren"); buttonPanel.add(button1); JButton button2 = new JButton("Motif"); button2.addActionListener(this); button2.setToolTipText("Motif-Look-and-Feel aktivieren"); buttonPanel.add(button2); JButton button3 = new JButton("Windows"); button3.addActionListener(this); button3.setToolTipText("Windows-Look-and-Feel aktivieren"); buttonPanel.add(button3); buttonPanel.setBorder(BorderFactory.createEtchedBorder()); getContentPane().add("South", buttonPanel); //Windows-Listener addWindowListener(new WindowClosingAdapter(true)); } public void actionPerformed(ActionEvent event) { String cmd = event.getActionCommand(); try { //PLAF-Klasse auswählen String plaf = "unknown"; if (cmd.equals("Metal")) { plaf = "javax.swing.plaf.metal.MetalLookAndFeel"; } else if (cmd.equals("Motif")) { plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; } else if (cmd.equals("Windows")) { plaf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; } //LAF umschalten UIManager.setLookAndFeel(plaf); SwingUtilities.updateComponetTreeUI(this); } catch (UnsupportedLookAndFeelException e) { System.err.println(e.toString()); } catch (ClassNotFoundException e) { System.err.println(e.toString()); } catch (InstantiationException e) { System.err.println(e.toString()); } catch (IllegalAccessException e) { System.err.println(e.toString()); } } public static void main(String[] args) { Geburtsmonat frame = new Geburtsmonat(); 34 35 frame.setLocation(100, 100); frame.pack(); frame.setVisible(true); } } Einige Besonderheiten von Swing Unten befindet sich ein Panel mit drei Buttons, mit denen zwischen den drei vordefinierten Look-and-Feels umgeschaltet werden kann Die beiden Panels wurden mit einer Umrandung versehen, und alle aktiven Elemente zeigen Tooltips an, wenn man mit dem Mauszeiger darauf zeigt. Das Label besitzt neben seiner Beschriftung ein Icon. 1.2.2 Probleme des AWT Da alle Fenster- und Dialogelemente von dem darunterliegenden Betriebssystem zur Verfügung gestellt wurden, ist es sehr schwierig, plattformübergreifend ein einheitliches Look-and-Feel zu realisieren. Im AWT gibt es nur eine Grundmenge an Dialogelementen, mit denen sich aufwendige grafische Benutzeroberflächen nicht oder nur mit sehr viel Zusatzaufwand realisieren lassen. 1.2.3 Leichtgewichtige Komponenten Im Gegensatz zum AWT benutzen Swing-Komponenten nur noch in sehr eingeschränkter Weise plattformspezifische GUI-Ressourcen. Ein Swing-Button unter Windows wird nicht mehr vom Windows-UI-Manager dargestellt, sondern von Swing selbst gezeichnet Der Grundstein für Komponenten, die von Java selbst erzeugt und dargestellt werden können, wurde im JDK 1.1 mit der Einführung der Lightweight Components (»leichtgewichtige« Komponenten) gelegt. Dabei wird die paint-Methode eines ComponentObjects nicht mehr an die betriebssystemspezifische Klasse weitergeleitet, sondern in den Komponentenklassen überlagert und mit Hilfe grafischer Primitivoperationen selbst implementiert. Die im AWT vorhandenen Dialogelemente werden im Gegensatz dazu als Heavyweight Components bezeichnet (»schwergewichtige« Komponenten). Dass alle Komponenten selbst gezeichnet werden müssen, erfordert viel CPU-Leistung und eine Menge Hauptspeicher. 1.2.4 Pluggable Look-and-Feel Eine der auf den ersten Blick spektakulärsten Eigenschaften von Swing ist die Möglichkeit, das Look-and-Feel (also das Aussehen und die Bedienung einer Anwendung) zur Laufzeit umschalten zu können, z.B. zwischen Swing, Motif und Windows (Pluggable Look-and-Feel ). 1.2.5 Das Model-View-Controller-Prinzip Neben den äußerlichen Qualitäten wurde auch die Architektur des Gesamtsystems verbessert. Wichtigste "Errungenschaft" ist dabei das Model-View-Controller-Prinzip (kurz MVC genannt). Anstatt den gesamten Code in eine einzelne Klasse zu packen, werden beim MVC-Konzept drei unterschiedliche Bestandteile eines grafischen Elements sorgsam unterschieden: Das Modell enthält die Daten des Dialogelements und speichert seinen Zustand. Der View ist für die grafische Darstellung der Komponente verantwortlich. Der Controller wirkt als Verbindungsglied zwischen beiden. Er empfängt Tastatur- und Mausereignisse und stößt die erforderlichen Maßnahmen zur Änderung von Model und View an. 35 36 Das Modell enthält praktisch die gesamte Verarbeitungslogik der Komponente. Ein wichtiger Aspekt ist dabei, dass ein Model mehrere Views gleichzeitig haben kann. Damit Veränderungen des Modells in allen Views sichtbar werden, wird ein Benachrichtigungsmechanismus implementiert, mit dem das Modell die zugeordneten Views über Änderungen informiert. Bei den Swing-Dialogelementen wird eine vereinfachte Variante von MVC verwendet, die auch als Model-Delegate-Prinzip bezeichnet wird. Hierbei wird die Funktionalität von View und Controller in einem UI Delegate zusammengefasst. 1.2.6 Container und Menüs 1.2.6.1 Hauptfenster 1.2.6.1.1 Beispiel MDI /* Mdi.java */ class DesktopFrame extends JFrame { private JDesktopPane desk; public DesktopFrame() { super("DesktopFrame"); this.desk = new JDesktopPane(); desk.setDesktopManager(new DefaultDesktopManager()); setContentPane(desk); addWindowListener(new WindowClosingAdapter()); } public void addChild(JInternalFrame child, int x, int y) { child.setLocation(x, y); child.setSize(200, 150); child.setDefaultCloseOperation( JInternalFrame.DISPOSE_ON_CLOSE ); desk.add(child); child.setVisible(true); } } class ChildFrame extends JInternalFrame { public ChildFrame(String title) { super("Child " + title, true, true); setIconifiable(true); setMaximizable(true); setBackground(Color.green); } } public class Mdi { public static void main(String[] args) { //Desktop erzeugen DesktopFrame desktop = new DesktopFrame(); desktop.setLocation(100, 100); desktop.setSize(400, 300); desktop.setVisible(true); //Zwei ChildFrames hinzufügen desktop.addChild(new ChildFrame("1"), 10, 10); desktop.addChild(new ChildFrame("2"), 20, 20); } } 36 37 1.2.6.1.2 JFrame 1.2.6.1.2.1 Struktur Ein bedeutender Unterschied zwischen den AWT- und Swing-Hauptfenstern besteht in ihrer Komponentenstruktur und den sich daraus ergebenden Unterschieden in der Bedienung. Während die Komponenten eines AWT-Fensters direkt auf dem Fenster plaziert werden, besitzt ein Swing-Hauptfenster eine einzige Hauptkomponente, die alle anderen Komponenten aufnimmt. Diese Hauptkomponente wird als RootPane bezeichnet und ist vom Typ JRootPane. Sie übernimmt die Rolle einer Verwaltungsinstanz für alle anderen Komponenten des Hauptfensters. Eine RootPane enthält folgende Komponenten: Eine aus Container abgeleitete GlassPane und eine aus JLayeredPane abgeleitete LayeredPane. Die LayeredPane enthält ihrerseits zwei Unterkomponenten: Eine aus Container abgeleitete ContentPane und Eine aus JMenuBar abgeleitete Menüleiste. Dabei ergibt sich folgende Struktur: Die GlassPane ist normalerweise durchsichtig und wird meist nicht zur Grafikausgabe benutzt. Sie könnte dann verwendet werden, wenn Effekte erzielt werden sollen, die das Fenster als Ganzes betreffen (und nicht seine einzelnen Dialogelemente). Eine (beispielsweise von JInternalFrame genutzte) Funktion besteht darin, Mausereignisse abzufangen, bevor sie an andere Komponenten weitergegeben werden. Die LayeredPane enthält das Menü und die Dialogelemente der Anwendung. 1.2.6.1.2.2 Erzeugung Die RootPane, und mit ihr die darin enthaltene GlassPane, LayeredPane und ContentPane, werden beim Anlegen des Fensters automatisch erzeugt (einzig die Menüleiste bleibt standardmäßig leer). 1.2.6.1.2.3 Zugriff Alle Hauptfenster implementieren das Interface RootPaneContainer, das den Zugriff auf die RootPane vereinfacht. Einige seiner Methoden sind: public JRootPane getRootPane() 37 38 public Container getContentPane() public JLayeredPane getLayeredPane() public Component getGlassPane() javax.swing.RootPaneContainer Um auf einem Hauptfenster Komponenten zu plazieren, ist es also nicht nötig, zunächst mit getRootPane die RootPane, dann mit getLayeredPane die LayeredPane und schließlich mit getContentPane die ContentPane zu beschaffen, sondern es kann direkt getContentPane aufgerufen werden. Das Einfügen und Anordnen von Dialogelementen auf einem Hauptfenster erfolgt über dessen ContentPane. Die Aufrufe von add und setLayout werden damit nicht direkt auf dem Fenster ausgeführt, sondern auf dessen ContentPane, die über einen Aufruf von getContentPane beschafft werden kann. 1.2.6.1.3 JInternalFrame 1.2.6.1.3.1 Konzept Bei vielen Programmen ist es üblich, dass sie ein einziges Hauptfenster besitzen und ihre zahlreichen, gleichzeitig geöffneten Kindfenster innerhalb dieses Hauptfensters anordnen. Diese unter Windows als MDI (Multiple Document Interface) bezeichnete Technik ist bei bestimmten Typen von Anwendungen mittlerweile weitverbreitet (z.B. bei Textverarbeitungen, Grafikprogrammen oder Entwicklungsumgebungen). 1.2.6.1.3.2 Der Desktop Um die Kindfenster zu verwalten, wird die vordefinierte ContentPane durch eine Instanz der Klasse JDesktopPane ersetzt. Diese Klasse besitzt einen DesktopManager, der für die Verwaltung der Kindfenster zuständig ist. Der DesktopManager wird beispielsweise benachrichtigt (und führt alle dazu erforderlichen Aktionen aus), wenn ein Kindfenster verkleinert, vergrößert oder verschoben werden soll. Der DesktopManager ist ein Interface, das eine Vielzahl von Methoden enthält. Mit der Klasse DefaultDesktopManager gibt es eine Standardimplementierung, die für viele Zwecke ausreichend ist. 1.2.6.1.3.3 Die Kindfenster Die Kindfenster werden aus JInternalFrame abgeleitet. JInternalFrame implementiert das Interface RootPaneContainer. Mit setDefaultCloseOperation wird angegeben, wie das Fenster sich beim Schließen verhalten soll. Hier kann eine der Konstanten DO_NOTHING_ON_CLOSE, HIDE_ON_CLOSE oder DISPOSE_ON_CLOSE angegeben werden. 1.2.6.1.4 Beispiel SplashScreen swing SplashScreen nur elektronisch 1.2.6.1.5 JWindow Sie ist aus Window abgeleitet und dient wie diese dazu, ein rahmenloses Fenster zu erzeugen, das an beliebiger Stelle und in beliebiger Größe auf dem Bildschirm plaziert werden kann. 38 39 Ebenso wie JFrame besitzt auch JWindow eine RootPane mit der im vorigen Abschnitt beschriebenen Struktur. 1.2.6.1.6 JDialog Die aus Dialog abgeleitete Klasse JDialog besitzt denselben strukturellen Aufbau wie JFrame und JWindow. Als owner sollte der Aufrufer dem Konstruktor das Fenster übergeben, zu dem der Dialog logisch gehört. 1.2.6.1.7 Beispiel Quartal Swing Quartal Zeigt die Verwendung von JOptionPane nur elektronisch 1.2.6.1.8 JOptionPane Eine weitere Möglichkeit, Swing-Dialoge zu erzeugen, steht mit der Klasse JOptionPane zur Verfügung. Diese ist in der Lage, einfache Dialoge, die lediglich ein Icon und einen Text oder ein Eingabefeld und eine Auswahl der Buttons "Yes", "No" und "Cancel" enthalten, mit einem einzigen Aufruf einer statischen Methode zu erzeugen. 1.2.6.1.9 JApplet Die Klasse JApplet ist eine einfache Erweiterung von java.applet.Applet. Sie dient zur Entwicklung von Applets, die Swing-Dialogelemente zur Gestaltung der Oberfläche verwenden. Wie bei den anderen Hauptfenstern ist auch hier der wichtigste Unterschied, dass JApplet die beschriebene RootPane-Struktur realisiert. 39 40 1.2.6.2 Menüs 1.2.6.2.1 Einfache Menüs 1.2.6.2.1.1 Beispiel SimpleMenue Swing SimpleMenue /* SimpleMenue.java */ public class SimpleMenue extends JFrame implements ActionListener { public SimpleMenue() { super("Swing-Menütest"); addWindowListener(new WindowClosingAdapter()); JMenuBar menubar = new JmenuBar(); menubar.add(createFileMenu()); setJMenuBar(menuBar); } public void actionPerformed(ActionEvent event) { System.out.println(event.getActionCommand()); } //---Private Methoden--------------private JMenu createFileMenu() { JMenu menu = new JMenu("Datei"); menu.setMnemonic('D'); JMenuItem mi; //Öffnen mi = new JMenuItem("Oeffnen", 'f'); setCtrlAccelerator(mi, 'O'); mi.addActionListener(this); menu.add(mi); //Speichern mi = new JMenuItem("Speichern", 'p'); setCtrlAccelerator(mi, 'S'); mi.addActionListener(this); menu.add(mi); //Separator menu.addSeparator(); //Beenden mi = new JMenuItem("Beenden", 'e'); mi.addActionListener(this); menu.add(mi); return menu; } private void setCtrlAccelerator(JMenuItem mi, char acc) { KeyStroke ks = KeyStroke.getKeyStroke( acc, Event.CTRL_MASK ); mi.setAccelerator(ks); } public static void main(String[] args) { SimpleMenue frame = new SimpleMenue(); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } Alt D erzeugt DropDownMenue f öffnet File Ctrl O öffnet File auch ohne DropDownMenue 40 41 1.2.6.2.1.2 Grundlagen von Swing-Menüs JMenuBar In Swing können alle Hauptfenster mit Ausnahme von JWindow eine Menüleiste haben. Dabei handelt es sich um eine Instanz der Klasse JMenuBar, die dem Hauptfenster durch Aufruf von addJMenuBar hinzugefügt wird. JMenu Die einzelnen Menüs einer Menüleiste sind Instanzen der Klasse JMenu, die aus JMenuItem abgeleitet ist (Composite-Pattern). Mit addSeparator wird eine Trennlinie hinter dem letzten Menüpunkt angefügt. JMenuItem Die Klasse JMenuItem repräsentiert Menüeinträge, also Elemente, die sich in einem Menü befinden. Dabei handelt es sich um Texte, die wahlweise mit einem Icon oder einem Häkchen versehen werden können. Der an den Konstruktor übergebene String text legt den Menütext fest. Wahlweise kann zusätzlich ein Icon übergeben werden, das neben dem Menütext angezeigt wird. Mit setMnemonic wird das mnemonische Kürzel des Menüeintrags festgelegt. Das ist ein (unterstrichen dargestellter) Buchstabe innerhalb des Menütexts, der bei geöffnetem Menü gedrückt werden kann, um den Menüeintrag per Tastatur aufzurufen. Die als Acceleratoren oder Beschleunigertasten bezeichneten Tasten können auch dann verwendet werden, wenn das entsprechende Menü nicht geöffnet ist. Beschleuniger werden mit setAccelerator zugewiesen. Mit den Methoden setEnabled und getEnabled kann auf den Aktivierungszustand des Menüeintrags zugegriffen werden. Wird false an setEnabled übergeben, wird der Eintrag deaktiviert, andernfalls aktiviert. 1.2.6.2.2 Weitere Möglichkeiten 1.2.6.2.2.1 Beispiel Extended Menue ( funktioniert nicht mit VisualCafe) Achtung Fehler bei Verwendung von Visual Cafe : Im JDK 1.3 arbeitet das Programm unter Windows fehlerhaft. Nach einmaligem Anklicken des Menüeintrags »Sicherheit« kann das Untermenü »Tools« nicht mehr per Maus aufgerufen werden, weil im Event-Thread eine NullPointerException ausgelöst wird. Die Tastaturbedienung ist dagegen weiterhin möglich. Dieser seit dem RC2 bekannte Fehler wurde leider bis zur endgültigen Version des JDK 1.3 nicht mehr behoben. JDK1.1-1.3 /* ExtendedMenue.java */ public class ExtendedMenue extends JFrame { public ExtendedMenue() { super("Extended Menue"); addWindowListener(new WindowClosingAdapter()); JMenuBar menubar = new JMenuBar(); menubar.add(createExtrasMenu()); setJMenuBar(menubar); } //---Private Methoden--------------private JMenu createExtrasMenu() { JMenu ret = new JMenu("Extras"); 41 42 ret.setMnemonic('X'); JMenuItem mi; //Tools-Untermenü ret.add(createToolsSubMenu()); //Separator ret.addSeparator(); //Statuszeile und Buttonleiste mi = new JCheckBoxMenuItem("Statuszeile"); mi.setMnemonic('z'); ((JCheckBoxMenuItem)mi).setState(true); ret.add(mi); mi = new JCheckBoxMenuItem("Buttonleiste"); mi.setMnemonic('B'); ret.add(mi); //Separator ret.addSeparator(); //Offline, Verbinden, Anmelden ButtonGroup bg = new ButtonGroup(); mi = new JRadioButtonMenuItem("Offline", true); mi.setMnemonic('O'); ret.add(mi); bg.add(mi); mi = new JRadioButtonMenuItem("Verbinden"); mi.setMnemonic('V'); ret.add(mi); bg.add(mi); mi = new JRadioButtonMenuItem("Anmelden"); mi.setMnemonic('A'); ret.add(mi); bg.add(mi); //Separator ret.addSeparator(); //Sicherheit mi = new JMenuItem( "Sicherheit", new ImageIcon("lock.gif") ); mi.setMnemonic('S'); mi.setHorizontalTextPosition(JMenuItem.LEFT); ret.add(mi); return ret; } private JMenu createToolsSubMenu() { JMenu ret = new JMenu("Tools"); ret.setMnemonic('T'); ret.add(new JMenuItem("Rechner", ret.add(new JMenuItem("Editor", ret.add(new JMenuItem("Browser", ret.add(new JMenuItem("Zipper", ret.add(new JMenuItem("Snapper", ret.add(new JMenuItem("Viewer", return ret; } 'R')); 'E')); 'B')); 'Z')); 'S')); 'V')); public static void main(String[] args) { ExtendedMenue frame = new ExtendedMenue(); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } 1.2.6.2.2.2 Untermenüs Da Menu aus MenuItem abgeleitet ist, kann an die Methode add der Klasse Menu auch eine Instanz der Klasse Menu übergeben werden. Auf diese Weise lassen sich Menüs schachteln. Der Name des Untermenüs erscheint dann an der Einfügestelle, und mit einem kleinen Pfeil wird angezeigt, dass es sich um ein Untermenü handelt. 1.2.6.2.2.3 Icons in Menüeinträgen 42 43 Einem Menüeintrag kann auch ein Icon zugeordnet werden. Icon ist ein Interface, das die abstrakten Eigenschaften eines Icons definiert. Es besitzt eine Implementierung ImageIcon, mit der sehr einfach aus einer gif- oder jpeg-Datei ein Icon erzeugt werden kann: 1.2.6.2.2.4 Checkboxes und Radiobuttons in Menüeinträgen Die Kontrolle des Zustands der Buttons erfolgt mit einem ButtonGroup-Objekt. Es wird vor dem Erzeugen der Menüeinträge angelegt, und jeder JRadioButtonMenuItem wird mit add hinzugefügt: Das ButtonGroup-Objekt sorgt automatisch dafür, dass zu jedem Zeitpunkt genau ein Eintrag selektiert ist. 1.2.6.2.3 Kontextmenue 1.2.6.2.3.1 Beispiel Kontextmenue nur elektronisch 1.2.6.2.3.2 Kontextmenüs In Swing-Programmen werden Kontextmenüs mit Hilfe der Klasse JPopupMenu erzeugt. Um das Menü anzuzeigen, ist show aufzurufen. Die dazu erforderlichen Koordinaten gewinnt man am besten aus der aktuellen Position des Mauszeigers, die im Mausereignis mitgeliefert wird. Die zum Aktivieren eines Kontextmenüs erforderliche Mausaktion kann von Plattform zu Plattform unterschiedlich sein. Die portabelste Lösung besteht darin, einen MouseListener auf der Komponente zu registrieren, und bei jedem Mausereignis mit isPopupTrigger abzufragen, ob es sich um eine Aktion zum Aufrufen eines Kontextmenüs handelte. 1.2.7 Swing Komponenten 1.2.7.1 Einfache Swing Komponenten 1.2.7.1.1 Beispiel Swing Components Swing SwingComponents /* SwingComponents.java */ public class SwingComponents extends JFrame implements ActionListener, CaretListener, AdjustmentListener, ChangeListener { // Fuer Liste static final String[] DATA = { "Hund", "Katze", "Meerschweinchen", "Tiger", "Maus", "Fisch", "Leopard", "Schimpanse", "Kuh", "Pferd", "Reh", "Huhn", "Marder", "Adler", "Nilpferd" }; private JList list; // Fuer Combo Box private static final String[] COLORS = { "rot", "grün", "blau", "gelb" }; // Fuer Radio Buttons private ButtonGroup group = new ButtonGroup(); // Fuer Scrollbar 43 44 private private private private private private private JPanel coloredPanel; JScrollBar sbEast; JScrollBar sbSouth; JSlider slWest; JSlider slNorth; int blue = 100; int red = 200; public SwingComponents() { super("Swing Components"); addWindowListener(new WindowClosingAdapter()); Container cp = getContentPane(); cp.setLayout(new GridLayout(4, 2)); // Text JPanel tp = new JPanel(); tp.setBorder(BorderFactory.createLineBorder(Color.blue)); // Textfields JTextField tf; //Linksbündiges Textfeld mit "Hello, world" tf = new JTextField("Hello, world"); tp.add(tf); //Leeres Textfeld mit 20 Spalten tf = new JTextField(20); tp.add(tf); //Textfeld mit "Hello, world" und 20 Spalten tf = new JTextField("Hello, world", 20); tf.addActionListener(this); tf.addCaretListener(this); tp.add(tf); // Textarea JTextArea ta = new JTextArea("Hello world \nHallo Welt \nHuhu \ng \nh \nEnde", 5, 30); ta.setTabSize(4); ta.setLineWrap(true); ta.setWrapStyleWord(true); tp.add(new JScrollPane(ta)); cp.add(tp); // Labels JPanel lp = new JPanel(); lp.setBorder(BorderFactory.createLineBorder(Color.blue)); lp.setLayout(new GridLayout(5, 1)); JLabel label; //Standardlabel label = new JLabel("Standard-Label"); lp.add(label); //Label mit Icon label = new JLabel( "Label mit Icon", new ImageIcon("lock.gif"), JLabel.CENTER ); lp.add(label); //Nur-Icon label = new JLabel(new ImageIcon("lock.gif")); lp.add(label); //Icon auf der rechten Seite label = new JLabel( "Label mit Icon rechts", new ImageIcon("lock.gif"), JLabel.CENTER ); label.setHorizontalTextPosition(JLabel.LEFT); lp.add(label); //Label rechts unten label = new JLabel("Label rechts unten"); label.setHorizontalAlignment(JLabel.RIGHT); label.setVerticalAlignment(JLabel.BOTTOM); lp.add(label); cp.add(lp); // Buttons JPanel bp = new JPanel(); bp.setBorder(BorderFactory.createLineBorder(Color.blue)); //OK-Button JButton okButton = new DefaultButton("OK", getRootPane()); okButton.addActionListener(this); 44 45 bp.add(okButton); //Abbrechen-Button JButton cancelButton = new CancelButton("Abbrechen"); cancelButton.addActionListener(this); bp.add(cancelButton); //Hilfe-Button JButton helpButton = new JButton("Hilfe"); helpButton.setMnemonic('H'); helpButton.addActionListener(this); bp.add(helpButton); //Test-Button JButton testButton = new JButton("Test", new ImageIcon("testicon.gif")); testButton.setMnemonic('T'); testButton.addActionListener(this); testButton.setHorizontalTextPosition(JButton.LEFT); testButton.setForeground(Color.blue); testButton.setBackground(Color.green); bp.add(testButton); //Panel hinzufügen cp.add(bp); // CheckBox JPanel cbp = new JPanel(); cbp.setBorder(BorderFactory.createLineBorder(Color.blue)); cbp.setLayout(new GridLayout(3, 1)); for (int i = 1; i <= 3; ++i) { JCheckBox cb = new JCheckBox("Checkbox" + i, i == 2); cb.addItemListener(new CheckBoxItemListener()); cbp.add(cb); } cp.add(cbp); // Radio Buttons //RadioButton-Panel JPanel rbp = new JPanel(); rbp.setBorder(BorderFactory.createLineBorder(Color.blue)); rbp.setLayout(new GridLayout(4, 1)); for (int i = 1; i <= 3; ++i) { JRadioButton rb = new JRadioButton("RadioButton" + i, i == 2); rb.setActionCommand(rb.getText()); rbp.add(rb); group.add(rb); } //Selektion-Button JButton button = new JButton("Selektion"); button.addActionListener(this); rbp.add(button); cp.add(rbp); // Liste //List-Panel JPanel lip = new JPanel(); lip.setBorder(BorderFactory.createLineBorder(Color.blue)); lip.setLayout(new BorderLayout()); list = new JList(new DefaultListModel()); ((DefaultListModel)list.getModel()).addElement("Hans"); ((DefaultListModel)list.getModel()).addElement("Franz"); ((DefaultListModel)list.getModel()).addElement("Heinz"); list.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); list.setSelectedIndex(2); lip.add(new JScrollPane(list), "Center"); //Ausgabe-Button JButton ab = new JButton("Ausgabe"); ab.addActionListener(this); lip.add(ab, "South"); cp.add(lip); // Combo Box //Combo Box-Panel JPanel cop = new JPanel(); cop.setBorder(BorderFactory.createLineBorder(Color.blue)); cop.setLayout(new BorderLayout()); for (int i = 1; i <= 2; ++i) { JPanel p = new JPanel(); p.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 2)); p.add(new JLabel("Farbe " + i + ":")); JComboBox combo = new JComboBox(COLORS); 45 46 combo.setEditable(i == 1); combo.addItemListener(new ComboBoxItemListener()); p.add(combo); cop.add(p, i == 1 ? "North" : "Center"); } cp.add(cop); // Scrollbar und Slider //Scrollbar-Panel JPanel sbp = new JPanel(); sbp.setBorder(BorderFactory.createLineBorder(Color.blue)); sbp.setLayout(new BorderLayout()); //Vertikaler Schieberegler sbEast = new JScrollBar(JScrollBar.VERTICAL, 0, 10, 0, 255); sbEast.addAdjustmentListener(this); sbp.add(sbEast, "East"); //Horizontaler Schieberegler sbSouth = new JScrollBar(JScrollBar.HORIZONTAL, 0, 10, 0, 255); sbSouth.addAdjustmentListener(this); sbp.add(sbSouth, "South"); //Vertikaler Slider slWest = new JSlider(JSlider.VERTICAL, 0, 255, 0); slWest.setMajorTickSpacing(50); slWest.setMinorTickSpacing(10); slWest.setPaintTicks(true); slWest.setPaintLabels(true); slWest.addChangeListener(this); sbp.add(slWest, "West"); //Horizontaler Schieberegler slNorth = new JSlider(JSlider.HORIZONTAL, 0, 255, 0); slNorth.setMajorTickSpacing(100); slNorth.setMinorTickSpacing(25); slNorth.setPaintTicks(true); slNorth.setPaintLabels(true); slNorth.setSnapToTicks(true); slNorth.addChangeListener(this); sbp.add(slNorth, "North"); //Farbiges Panel coloredPanel = new JPanel(); coloredPanel.setBackground(new Color(red, 0, blue)); sbp.add(coloredPanel, "Center"); cp.add(sbp); } public void actionPerformed(ActionEvent event) { String ec = event.getActionCommand(); if ((ec.equals("OK"))|| (ec.equals("Abbrechen"))|| (ec.equals("Test"))|| (ec.equals("Hilfe"))) { System.out.println(ec); } else if (ec.equals("Ausgabe")) { System.out.println("---"); ListModel lm = list.getModel(); int[] sel = list.getSelectedIndices(); for (int i = 0; i < sel.length; ++i) { String value = (String)lm.getElementAt(sel[i]); System.out.println(" " + value); } } else if (ec.equals("Selektion")) { ButtonModel selected = group.getSelection(); System.out.print("Selektiert: "); if (selected != null) { System.out.println(selected.getActionCommand()); } } else { JTextField tf = (JTextField)event.getSource(); System.out.println("---ActionEvent---"); System.out.println(tf.getText()); System.out.println(tf.getSelectedText()); System.out.println(tf.getSelectionStart()); System.out.println(tf.getSelectionEnd()); System.out.println(tf.getCaretPosition()); 46 47 } } public void caretUpdate(CaretEvent event) { System.out.println("---CaretEvent---"); System.out.println(event.getDot()); System.out.println(event.getMark()); } public void adjustmentValueChanged(AdjustmentEvent event) { JScrollBar sb = (JScrollBar)event.getSource(); if (sb == sbEast) { blue = event.getValue(); } else { red = event.getValue(); } coloredPanel.setBackground(new Color(red, 0, blue)); coloredPanel.repaint(); if (!sb.getValueIsAdjusting()) { System.out.println("Color : (" + red + ",0," + blue + ")"); } } public void stateChanged(ChangeEvent event) { JSlider sl = (JSlider)event.getSource(); if (sl == slWest) { blue = sl.getValue(); } else { red = sl.getValue(); } coloredPanel.setBackground(new Color(red, 0, blue)); coloredPanel.repaint(); if (!sl.getValueIsAdjusting()) { System.out.println("(" + red + ",0," + blue + ")"); } } public static void main(String[] args) { SwingComponents frame = new SwingComponents(); frame.setLocation(100, 100); frame.setSize(800, 600); frame.setVisible(true); } class CheckBoxItemListener implements ItemListener { public void itemStateChanged(ItemEvent e) { JCheckBox cb = (JCheckBox)e.getSource(); int change = e.getStateChange(); if (change == ItemEvent.SELECTED) { System.out.println(cb.getText() + ": SELECTED"); } else if (change == ItemEvent.DESELECTED) { System.out.println(cb.getText() + ": DESELECTED"); } } } class ComboBoxItemListener implements ItemListener { public void itemStateChanged(ItemEvent e) { JComboBox cb = (JComboBox)e.getSource(); System.out.println("ComboBox : " + cb.getSelectedItem()); } } } /* CancelButton.java */ import java.awt.event.*; //import javax.swing.*; import com.sun.java.swing.*; public class CancelButton extends JButton 47 48 { public CancelButton(String title) { super(title); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent event) { String cmd = event.getActionCommand(); if (cmd.equals("PressedESCAPE")) { doClick(1000); } } }; registerKeyboardAction( al, "PressedESCAPE", KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JButton.WHEN_IN_FOCUSED_WINDOW ); } } /* DefaultButton.java */ //import javax.swing.*; import com.sun.java.swing.*; public class DefaultButton extends JButton { public DefaultButton(String title, JRootPane rootpane) { super(title); rootpane.setDefaultButton(this); } } 1.2.7.1.2 Label und Textfelder 1.2.7.1.2.1 JLabel Ein JLabel ist ein Dialogelement zur Anzeige einer Beschriftung innerhalb eines GUIContainers. Es besitzt einen Text und ein Icon, die in beliebiger Anordnung dargestellt werden können.. Auf Benutzereingaben reagiert ein JLabel nicht. 1.2.7.1.2.2 JTextField Die Klasse JTextField ist das Swing-Pendant zur AWT-Klasse TextField und stellt ein einzeiliges Textfeld zur Eingabe von Daten dar. Die wichtigsten registrierbaren Listener sind ActionListener und CaretListener: Ein ActionListener wird immer dann aufgerufen, wenn im Eingabefeld [ENTER] gedrückt wird, ein CaretListener, wenn sich die Position der Einfügemarke geändert hat. (Einen TextListener wie bei der Klasse TextField gibt es bei den aus JTextComponent abgeleiteten Klassen nicht. Will die Anwendung über jede Änderung in einem Textfeld unterrichtet werden, muss sie einen DocumentListener auf dessen Modell registrieren. Das Modell eines Textfeldes ist eine Instanz der Klasse Document und kann durch Aufruf von getDocument beschafft werden.) 1.2.7.1.2.3 JPasswordField JPasswordField ist eine Spezialisierung von JTextField zur Eingabe von Passworten. Der Hauptunterschied zu JTextField besteht darin, dass der eingegebene Text nicht angezeigt, sondern statt dessen für jedes Zeichen ein Sternchen ausgegeben wird. Durch Aufruf von 48 49 setEchoChar kann auch ein anderes Ausgabezeichen ausgewählt werden. Des weiteren sind die beiden Zwischenablagefunktionen Kopieren und Ausschneiden deaktiviert, um zu verhindern, dass der Text aus einem Passwortfeld in ein anderes Feld kopiert oder an ein anderes Programm übergeben werden kann. 1.2.7.1.2.4 JTextArea JTextArea ist eine Komponente zur Anzeige und Eingabe von mehrzeiligen Texten. Wie die AWT-Klasse TextArea dient sie dazu, unformatierte Texte zu bearbeiten. Diese können zwar Zeilenumbrüche und Tabulatoren, nicht aber unterschiedliche Schriften, Farben oder grafische Elemente enthalten (für diesen Zweck gibt es die Klassen JEditorPane und JTextPane). Anders als TextArea ist JTextArea nicht in der Lage, Text automatisch zu scrollen, wenn er zu lang oder zu breit für den verfügbaren Bildschirmausschnitt ist. Wird das gewünscht, muss das Textfeld in eine Komponente des Typs JScrollPane eingebettet werden. An Stelle der JTextArea ist dem Dialog dann die JScrollPane hinzuzufügen. 1.2.7.1.3 Buttons 1.2.7.1.3.1 JButton Swing-Buttons sind Instanzen der Klasse JButton und dienen dazu, Schaltflächen zu erzeugen. Wird ein JButton per Mausklick betätigt, sendet er ein ActionEvent an alle registrierten Listener. Durch Aufruf von doClick kann ein Buttonklick auch programmgesteuert ausgelöst werden. 1.2.7.1.3.2 JCheckBox Die Klasse JCheckBox stellt einen Button dar, der vom Anwender wahlweise an- oder ausgeschaltet werden kann. Er wird meist verwendet, um boolesche Werte auf einer GUIOberfläche darzustellen. JCheckBox ist von der Klasse JToggleButton abgeleitet, die als Abstraktion von Buttons, die ihren Zustand ändern können, auch Basisklasse von JRadioButton ist. Das Ankreuzfeld einer JCheckBox ist also ihr (automatisch angezeigtes) Icon. Durch Aufruf von setHorizontalTextPosition und Übergabe der Konstante LEFT können Ankreuzfeld und Text vertauscht werden. Wie ein JButton sendet eine JCheckBox bei jeder Betätigung ein ActionEvent an registrierte Listener. Zudem wird bei Zustandsänderungen ein ItemEvent versendet, auf das ein ItemListener reagieren kann. 1.2.7.1.3.3 JRadioButton Die Klasse JRadioButton stellt einen Button dar, der wahlweise an- oder ausgeschaltet werden kann. Anders als bei JCheckBox ist in einer Gruppe von Radiobuttons allerdings immer nur ein Button zur Zeit aktiviert, alle anderen sind deaktiviert. Um die Buttons zu gruppieren, ist eine ButtonGroup zu instanzieren, und die Buttons sind durch Aufruf von add hinzuzufügen. Mit getSelection kann auf das ButtonModel des selektierten Elements zugegriffen werden, getElements liefert alle Buttons der Gruppe. 1.2.7.1.4 Listen und Comboboxen 49 50 1.2.7.1.4.1 JList Die Klasse JList dient dazu, Listen von Werten darzustellen, aus denen der Anwender einen oder mehrere Einträge auswählen kann. Im Gegensatz zur AWT-Klasse List kann sie nicht nur Strings, sondern beliebige Objekte enthalten. Auch die Darstellung der Listenelemente auf dem Bildschirm kann weitgehend frei gestaltet werden. Wird die Selektion geändert, versendet eine JList ein ListSelectionEvent an alle registrierten Listener. Jede Selektionsänderung führt zum Aufruf der Methode valueChanged. Den Listeninhalt dynamisch verändern Etwas mehr Aufwand als beim AWT-Pendant muss getrieben werden, wenn der Inhalt einer JList nach der Instanzierung modifiziert werden soll. In diesem Fall kann nicht mehr mit dem automatisch erzeugten Listenmodel gearbeitet werden, sondern es muss selbst eines erzeugt werden. Das Modell einer JList muss stets das Interface ListModel implementieren und der Liste durch Versenden eines ListDataEvent jede Datenänderung mitteilen. Eine für viele Zwecke ausreichende Implementierung steht mit der Klasse DefaultListModel zur Verfügung. Ihre Schnittstelle entspricht der Klasse Vector und alle erforderlichen Änderungsbenachrichtigungen werden automatisch verschickt. Soll eine Liste mit einem benutzerdefinierten Modell arbeiten, wird dieses einfach manuell erzeugt und an den Konstruktor übergeben. Alle Einfügungen, Löschungen und Änderungen von Daten werden dann an diesem Modell vorgenommen. Durch Aufruf von getModel kann auf einfache Weise auf das Modell einer JList zugegriffen werden. 1.2.7.1.4.2 JComboBox Eine JComboBox ist das Swing-Pendant zur AWT-Klasse Choice. Es stellt eine Kombination aus Textfeld und Liste dar. Die Liste ist normalerweise unsichtbar und wird vom Anwender nur dann geöffnet, wenn er einen Wert daraus auswählen will. Sie erlaubt grundsätzlich nur einfache Selektion. Ein wichtiges Merkmal einer JComboBox ist die Möglichkeit, das Textfeld editieren zu können oder nicht. Ist das der Fall, kann der Anwender auch Werte eingeben, die nicht in der Liste stehen; andernfalls ist er auf Listenwerte beschränkt. Mit den Methoden setEditable und isEditable kann auf diese Eigenschaft zugegriffen werden: Im Gegensatz zu JList stellt JComboBox auch einige Methoden zur Verfügung, mit denen die Elemente der Liste dynamisch verändert werden können. Mit addItem wird ein neues Element an das Ende der Liste angehängt, mit insertItemAt wird es einer beliebigen Position eingefügt. removeItem entfernt das angegebene Element, removeItemAt das Element mit dem angegebenen Index. removeAllItems entfernt alle Elemente aus der Liste. Jedesmal, wenn ein anderes Element selektiert wird, sendet eine JComboBox ein ItemEvent an registrierte ItemListener (und zwar sowohl für das deselektierte als auch für das selektierte Element). Nach Abschluß der Selektion oder wenn der Anwender in einer editierbaren JComboBox einen Wert per Hand eingegeben hat, wird zusätzlich ein ActionEvent an registrierte ActionListener versendet. 1.2.7.1.5 Quasi-analoge Komponenten 1.2.7.1.5.1 JScrollBar JScrollBar ist die leichtgewichtige Swing-Variante der AWT-Klasse Scrollbar. Sie dient dazu, mit Hilfe eines Schiebereglers einen Wert kontinuierlich innerhalb vorgegebener Grenzen einzustellen. 50 51 JScrollBar stellt einige Methoden zur Verfügung, mit denen nach der Instanzierung auf die numerischen Eigenschaften des Schiebereglers zugegriffen werden kann: Mit getMinimum, getMaximum, setMinimum und setMaximum kann auf das Minimum und Maximum des definierten Wertebereichs zugegriffen werden. getVisibleAmount liefert die Ausdehnung des Schiebers, und mit setVisibleAmount kann diese gesetzt werden. Mit getValue und setValue kann auf den aktuellen Wert des Schiebereglers zugegriffen werden. getUnitIncrement gibt an, um welchen Betrag der Wert des Schiebereglers verändert wird, wenn der Anwender einen der Pfeilbuttons betätigt. getBlockIncrement ermittelt analog dazu den Betrag der Änderung, wenn zwischen Schieber und Pfeilbuttons geklickt wird. Mit setUnitIncrement und setBlockIncrement können beide Werte auch verändert werden. Wird der Wert einer JScrollBar verändert, sendet sie ein AdjustmentEvent an registrierte Listener. Mit der Methode getValueIsAdjusting() kann festgestellt werden, auf welche Weise der Wert verändert wird. Sie gibt genau dann true zurück, wenn die Änderung Bestandteil einer Kette von Änderungen ist, wenn also der Anwender den Schieber betätigt. Wurde die Änderung dagegen durch einen Mausklick auf einen der Buttons oder auf die Fläche zwischen Buttons und Schieber ausgelöst, liefert getValueIsAdjusting() den Wert false. 1.2.7.1.5.2 JSlider Mit der Klasse JSlider werden ebenso wie mit JScrollBar Schieberegler erzeugt. Abgesehen von den unterschiedlichen Oberflächen gibt es zwischen beiden Klassen zwei wichtige konzeptionelle Unterschiede: Ein JSlider kann eine Anzeigeskala mit grober und feiner Einteilung und Beschriftung haben. Ein JSlider kennt keine unterschiedlichen Schiebergrößen. Ihre Ausdehnung ist immer 1. Mit setMajorTickSpacing wird der Abstand der großen Markierungen vorgegeben, mit setMinorTickSpacing der Abstand der kleinen. Damit die Anzeigeskala tatsächlich angezeigt wird, muss setPaintTicks aufgerufen und true übergeben werden. Soll auch die Beschriftung angezeigt werden, muss zusätzlich setPaintLabels mit true als Argument aufgerufen werden. Ein Aufruf von setSnapToTicks (mit Übergabe von true als Argument) sorgt dafür, dass der Schieber stets auf den Skalenmarkierungen einrastet. Zwischenpositionen können dann nicht mehr angewählt werden. Im Gegensatz zu JScrollPane sendet ein JSlider kein AdjustmentEvent, wenn sein Wert verändert wird, sondern ein ChangeEvent (diese Klasse liegt im Paket javax.swing.event). Wie bei JScrollPane kann mit getValueIsAdjusting festgestellt werden, ob die Änderung Bestandteil einer Kette von Wertänderungen ist oder ob sie einzeln aufgetreten ist. 51 52 1.2.7.2 Jtree 1.2.7.2.1 Beispiel SelectionTree /* SelectionTree.java */ import import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; javax.swing.tree.*; public class SelectionTree extends JFrame { TreeSelectionModel tsm = null; public SelectionTree() { super("JTree 2"); addWindowListener(new WindowClosingAdapter()); //Einfaches TreeModel bauen DefaultMutableTreeNode root, child, subchild; root = new DefaultMutableTreeNode("Root"); for (int i = 1; i <= 5; ++i) { String name = "Child-" + i; child = new DefaultMutableTreeNode(name); root.add(child); for (int j = 1; j <= 3; ++j) { subchild = new DefaultMutableTreeNode(name + "-" + j); child.add(subchild); } } //JTree erzeugen JTree tree = new JTree(root) tree.setRootVisible(true); //Einfachselektion aktivieren tsm = new DefaultTreeSelectionModel(); tsm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setSelectionModel(tsm); //TreeSelectionListener hinzufügen tree.addTreeSelectionListener( new TreeSelectionListener() { 52 53 public void valueChanged(TreeSelectionEvent event) { Treepath tp = event.getNewLeadSelectionPath(); if (tp != null) { System.out.println(" Selektiert: " + tp.toString()); int[]rows = tsm.getSelectionRows(); for (int i = 0; i < rows.length; i++) { System.out.println("row " + rows[i]); } } else { System.out.println(" Kein Element selektiert"); } } } ); //JTree einfügen Container cp = getContentPane(); cp.add(new JScrollPane(tree), "Center"); } public static void main(String[] args) { try { SelectionTree frame = new SelectionTree(); frame.setLocation(100, 100); frame.setSize(250, 200); frame.setVisible(true); } catch (Exception e) { } } } 1.2.7.2.2 Bäume JTree dient zur Darstellung, Navigation und Bearbeitung baumartiger, hierarchischer Datenstrukturen. Ein einfaches Beispiel für derartige Baumstrukturen stellt etwa das Dateisystem unter UNIX oder Windows dar. Es besteht aus einer Wurzel (dem Root-Verzeichnis) und darin enthaltenen Unterverzeichnissen. Die Unterverzeichnisse können ihrerseits weitere Unterverzeichnisse enthalten usw. Dadurch entsteht eine beliebig tief geschachtelte Struktur von Verzeichnissen, die baumartig durchlaufen und bearbeitet werden kann. Im Umgang mit Bäumen haben sich folgende Begriffe eingebürgert: Ein Element des Baums wird als Knoten bezeichnet. Die in einem Knoten enthaltenen Elemente bezeichnet man als Unterknoten, manchmal auch als Kindknoten. Das übergeordnete Element eines Knotens bezeichnet man als Vaterknoten, manchmal auch als Elternknoten. Das Startelement des Baums wird Wurzel genannt. Knoten, die keine Unterknoten haben, werden als Blätter bezeichnet. Knoten, die sowohl Vater- als auch Unterknoten enthalten, bezeichnet man als innere Knoten. 1.2.7.2.3 Interfaces für das Model-View Konzept Auch der JTree basiert auf dem Model-View Konzept. Der JTree erfragt die zur Darstellung und Navigation erforderlichen Daten immer bei seinem TreeModel. Ein TreeModel kapselt alle relevanten Informationen über die Struktur des Baums. Es liefert auf Anfrage dessen Wurzel, stellt Informationen über einen bestimmten Knoten zur Verfügung oder liefert dessen Unterknoten. Damit der JTree mit seinem TreeModel bzw. den TreeNodes kommunizieren kann, muss die Schnittstelle für TreeModel und TreeNode spezifiziert sein. 53 54 interface TreeNode Defines the requirements for an object that can be used as a tree node in a JTree. children() Returns the children of the reciever as an Enumeration. getAllowsChildren() Returns true if the receiver allows children. getChildAt(int childIndex) Returns the child TreeNode at index childIndex. getChildCount() Returns the number of children TreeNodes the receiver contains. getIndex(TreeNode node) Returns the index of node in the receivers children. getParent() Returns the parent TreeNode of the receiver. isLeaf() Returns true if the receiver is a leaf. interface MutableTreeNode extends TreeNode Defines the requirements for a tree node object that can change -- by adding or removing child nodes, or by changing the contents of a user object stored in the node. insert(MutableTreeNode child, int index) Adds child to the receiver at index. remove(MutableTreeNode node) Removes node from the receiver. remove(int index) Removes the child at index from the receiver. removeFromParent() Removes the receiver from its parent. setParent(MutableTreeNode newParent) Sets the parent of the receiver to newParent. setUserObject(Object object) Resets the user object of the receiver to object. interface TreeModel The interface that defines a suitable data model for a JTree. addTreeModelListener(TreeModelListener l) Adds a listener for the TreeModelEvent posted after the tree changes. getChild(Object parent, int index) Returns the child of parent at index index in the parent's child array. getChildCount(Object parent) Returns the number of children of parent. getIndexOfChild(Object parent, Object child) Returns the index of child in parent. getRoot() Returns the root of the tree. isLeaf(Object node) Returns true if node is a leaf. removeTreeModelListener(TreeModelListener l) Removes a listener previously added with addTreeModelListener(). valueForPathChanged(TreePath path, Object newValue) Messaged when the user has altered the value for the item identified by path to newValue. 1.2.7.2.4 Default-Implementationen der Interfaces Mit DefaultMutableTreeNode steht eine recht flexible Implementierung von TreeNode zur Verfügung, die auch Methoden zum Einfügen und Löschen von Knoten bietet. Mit add wird ein neuer Kindknoten an das Ende der Liste der Unterknoten angefügt, mit insert kann dies an einer beliebigen Stelle erfolgen. remove entfernt einen beliebigen und removeAllChildren alle Kindknoten. Anwendungsbezogene Informationen werden in einem 54 55 UserObject gehalten, das direkt an den Konstruktor übergeben werden kann. Mit setUserObject und getUserObject kann auch nach der Konstruktion noch darauf zugegriffen werden. Das UserObject ist auch der Lieferant für die Knotenbeschriftung: jeder Aufruf von toString wird an das UserObject weitergeleitet. DefaultTreeModel ist eine einfache Implementierung des TreeModels. 1.2.7.2.5 Erzeugen eines Baumes Die beiden wichtigsten Konstruktoren der Klasse JTree sind: public JTree(TreeModel newModel) public JTree(TreeNode root) Der erste von beiden erwartet ein vordefiniertes TreeModel zur Darstellung der Elemente des Baums. An den zweiten Konstruktor wird lediglich die Wurzel des Baums übergeben. Sie wird jedoch vom Konstruktor in das automatisch instanzierte DefaultTreeModel eingebettet. 1.2.7.2.6 Selektieren von Knoten Konfiguration der Selektionsmöglichkeit Das Selektieren von Knoten wird durch das TreeSelectionModel gesteuert. Standardmäßig erlaubt ein JTree das Selektieren mehrerer Knoten. Soll die Selektionsmöglichkeit auf einen einzelnen Knoten beschränkt werden, muss ein eigenes TreeSelectionModel an setSelectionModel übergeben werden. Dazu kann eine Instanz der Klasse DefaultTreeSelectionModel erzeugt und durch Aufruf von setSelectionMode und Übergabe einer der Konstanten SINGLE_TREE_SELECTION, CONTIGUOUS_TREE_SELECTION oder DISCONTIGUOUS_TREE_SELECTION konfiguriert werden: Abfragen der Selektion Mit getSelectionPath wird das selektierte Element ermittelt. Bei aktivierter Mehrfachselektion liefert die Methode das erste aller selektierten Elemente. Ist kein Knoten selektiert, wird null zurückgegeben. getSelectionPaths gibt ein Array mit allen selektierten Knoten zurück. getLeadSelectionPath liefert das zuletzt markierte Element. (public TreePath getLeadSelectionPath() Returns the path of the last node added to the selection.) Alle beschriebenen Methoden liefern Objekte des Typs TreePath. Diese Klasse beschreibt einen Knoten im Baum über den Pfad, der von der Wurzel aus beschritten werden muss, um zu dem Knoten zu gelangen. Mit getLastPathComponent kann das letzte Element dieses Pfads bestimmt werden. Mit getPath kann der komplette Pfad ermittelt werden. An erster Stelle liegt dabei die Wurzel des Baums, an letzter Stelle das selektierte Element: Soll ermittelt werden, ob und welche Elemente im Baum selektiert sind, können die Methoden isSelectionEmpty und isPathSelected aufgerufen werden: (Alternativ zum TreePath kann auf die selektierten Elemente auch mit Hilfe ihrer internen Zeilennummer zugriffen werden. Dazu besitzt jedes angezeigte Element im Baum eine fortlaufende Nummer, die mit 0 bei der Wurzel beginnt und sich dann zeilenweise bis zum letzten Element fortsetzt. Die zugehörigen Methoden heißen getSelectionRows und getLeadSelectionRow. Abhängig davon, wie viele Knoten oberhalb eines bestimmten 55 56 Knotens sichtbar oder verdeckt sind, kann sich die Zeilennummer während der Lebensdauer des Baums durchaus verändern.) Dient der JTree zur Steuerung anderer Komponenten (etwa in explorerartigen Oberflächen), muss das Programm meist unmittelbar auf Änderungen der Selektion durch den Anwender reagieren. Dazu kann es einen TreeSelectionListener instanzieren und ihn mit addTreeSelectionListener beim JTree registrieren. Bei jeder Selektionsänderung wird dann die Methode valueChanged aufgerufen und bekommt ein TreeSelectionEvent als Argument übergeben: Dieses stellt unter anderem die Methoden getOldLeadSelectionPath und getNewLeadSelectionPath zur Verfügung, um auf den vorherigen oder aktuellen Selektionspfad zuzugreifen: Verändern der Selektion Die Selektion kann auch programmgesteuert verändert werden: Mit clearSelection wird die Selektion vollständig gelöscht. Mit addSelectionPath und addSelectionPaths kann die Selektion um ein einzelnes oder eine Menge von Knoten erweitert werden. Mit setSelectionPath und setSelectionPaths werden - unabhängig von der bisherigen Selektion - die als Argument übergebenen Knoten selektiert. Öffnen und Schließen der Knoten Der Anwender kann die Knoten mit Maus- oder Tastaturkommandos öffnen oder schließen. Dadurch werden die Unterknoten entweder sichtbar oder versteckt. Das Programm kann diesen Zustand mit den Methoden isCollapsed und isExpanded abfragen: isExpanded liefert true, wenn der Knoten geöffnet ist, isCollapsed, wenn er geschlossen ist. hasBeenExpanded gibt an, ob der Knoten überhaupt schon einmal geöffnet wurde. isVisible gibt genau dann true zurück, wenn der Knoten sichtbar ist, d.h. wenn alle seine Elternknoten geöffnet sind. Mit makeVisible kann ein Knoten sichtbar gemacht werden. Mit expandPath kann er geöffnet und mit collapsePath geschlossen werden. 1.2.7.2.7 Beispiel MutableTree Das folgende Programm zeigt einen Baum, der zunächst nur den Wurzelknoten enthält. Dieser ist vom Typ DefaultMutableTreeNode und wird in ein explizit erzeugtes DefaultTreeModel eingebettet, dass an den Konstruktor des JTree übergeben wird. Neben dem JTree enthält das Programm drei Buttons, mit denen ein neuer Knoten eingefügt sowie ein vorhandener gelöscht oder seine Beschriftung geändert werden kann. /* MutableTree.java */ import import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; javax.swing.tree.*; public class MutableTree extends JFrame implements ActionListener { protected DefaultMutableTreeNode root; protected DefaultTreeModel treeModel; protected JTree tree; public MutableTree() { super("MutableTree"); addWindowListener(new WindowClosingAdapter()); 56 57 //JTree erzeugen und Einfachselektion aktivieren root = new DefaultMutableTreeNode("Root"); treeModel = new DefaultTreeModel(root); tree = new JTree(treeModel); TreeSelectionModel tsm = new DefaultTreeSelectionModel(); tsm.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setSelectionModel(tsm); tree.setRootVisible(true); //JTree einfügen Container cp = getContentPane(); cp.add(new JScrollPane(tree), BorderLayout.CENTER); //ButtonPanel JPanel panel = new JPanel(new FlowLayout()); String[] buttons = new String[]{"AddChild", "Delete", "Change"}; for (int i = 0; i < buttons.length; ++i) { JButton button = new JButton(buttons[i]); button.addActionListener(this); panel.add(button); } cp.add(panel, BorderLayout.SOUTH); } public void actionPerformed(ActionEvent event) { String cmd = event.getActionCommand(); TreePath tp = tree.getLeadSelectionPath(); if (tp != null) { DefaultMutableTreeNode node; node = (DefaultMutableTreeNode)tp.getLastPathComponent(); if (cmd.equals("AddChild")) { DefaultMutableTreeNode child; child = new DefaultMutableTreeNode("child"); treeModel.insertNodeInto(child, node, node.getChildCount()); TreeNode[] path = treeModel.getPathToRoot(node); tree.expandPath(new TreePath(path)); } else if (cmd.equals("Delete")) { if (node != root) { TreeNode parent = node.getParent(); treeModel.removeNodefromParent(node); TreeNode[] path = treeModel.getPathToRoot(parent); tree.setSelectionPath(new TreePath(path)); } } else if (cmd.equals("Change")) { String name = node.toString(); node.setUserObject(name + "C"); treeModel.nodeChanged(node); } } } public static void main(String[] args) { try { String plaf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; UIManager.setLookAndFeel(plaf); MutableTree frame = new MutableTree(); frame.setLocation(100, 100); frame.setSize(300, 300); frame.setVisible(true); } catch (Exception e) { } } } 1.2.7.2.8 Verändern der Baumstruktur Es ist ohne weiteres möglich, den Inhalt und die Struktur des Baums nach dem Anlegen des JTree zu ändern. Es können neue Knoten eingefügt, bestehende entfernt oder vorhandene modifiziert werden. Wird die Klasse DefaultMutableTreeNode als Knotenklasse verwendet, reicht es allerdings nicht aus, einfach die entsprechenden Methoden zum Ändern, Einfügen oder Löschen auf den betroffenen Knoten aufzurufen. In diesem Fall würde zwar die Änderung im Datenmodell durchgeführt werden, aber die Bildschirmdarstellung würde sich nicht verändern. Änderungen im Baum müssen immer über das Modell ausgeführt werden, denn nur dort ist der JTree standardmäßig als TreeModelListener registriert und wird 57 58 über Änderungen unterrichtet. Werden diese dagegen direkt auf den Knoten ausgeführt, bleiben sie dem Modell verborgen und die Anzeige wird inkonsistent. Für einfache Änderungen reicht es aus, eine Instanz der Klasse DefaultTreeModel als TreeModel zu verwenden. Sie wird durch Übergabe des Wurzelknotens instanziert und stellt eine Vielzahl von Methoden zum Einfügen, Löschen und Ändern der Knoten zur Verfügung. Alle Änderungen werden durch Versenden eines TreeModelEvent automatisch an alle registrierten TreeModelListener weitergegeben und führen dort zu entsprechenden Aktualisierungen der Bildschirmdarstellung. Mit insertNodeInto wird ein neuer Kindknoten an einer beliebigen Position zu einem Elternknoten hinzugefügt. Mit removeNodeFromParent wird ein beliebiger Knoten aus dem Baum entfernt (er darf auch Unterknoten enthalten), und nodeChanged sollte aufgerufen werden, wenn der Inhalt eines Knotens sich so geändert hat, dass seine Bildschirmdarstellung erneuert werden muss. getPathToRoot schließlich ist eine nützliche Hilfsmethode, mit der das zur Konstruktion eines TreePath-Objekts erforderliche KnotenArray auf einfache Weise erstellt werden kann. 1.2.7.3 JTabbedPane 1.2.7.3.1 Beispiel TabbedPane Das folgende Programm zeigt ein einfaches Beispiel eines Registerdialogs. Es erstellt ein JTabbedPane mit fünf Registerkarten, die jeweils ein JPanel mit einem Label und einem Button enthalten. Das Label zeigt den Namen der Karte an, der Button dient dazu, die jeweils nächste Karte auszuwählen. Dessen Aktivität ist in der Klasse NextTabActionListener gekapselt. Als Besonderheit wird nach jedem Seitenwechsel requestDefaultFocus auf der neuen Seite aufgerufen, um automatisch dem ersten Dialogelement den Fokus zu geben. /* TabbedPane.java */ import java.awt.*; import java.awt.event.*; import javax.swing.*; public class TabbedPane extends JFrame { JTabbedPane tp; public TabbedPane() { super("JTabbedPane"); addWindowListener(new WindowClosingAdapter()); tp = new JTabbedPane(); for (int i = 0; i < 5; ++i) { JPanel panel = new JPanel(); panel.add(new JLabel("Karte " + i)); JButton next = new JButton("Weiter"); next.addActionListener(new NextTabActionListener()); panel.add(next); tp.addTab("Tab" + i, panel); } getContentPane().add(tp, "Center"); } class NextTabActionListener implements ActionListener { public void actionPerformed(ActionEvent event) { int tab = tp.getSelectedIndex(); tab = (tab >= tp.getTabCount() - 1 ? 0 : tab + 1); tp.setSelectedIndex(tab); ((JPanel)tp.getSelectedComponent()).requestDefaultFocus(); } } public static void main(String[] args) { 58 59 TabbedPane frame = new TabbedPane(); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } 1.2.7.3.2 JTabbedPane Mit JTabbedPane ist es möglich, Dialoge zu erstellen, die eine Reihe von Registerkarten enthalten. Das sind Unterdialoge, die über ein am Rand befindliches Register einzeln ausgewählt werden können. Derartige Registerkarten werden beispielsweise in Konfigurationsdialogen verwendet, wenn die zu bearbeitenden Optionen nicht alle auf eine Seite passen. Nach der Instanzierung ist der Registerdialog zunächst leer. Um Registerkarten hinzuzufügen, kann eine der Methoden addTab oder insertTab aufgerufen werden: removeTabAt entfernt die Karte mit dem angegebenen Index, removeAll entfernt alle Karten. Mit getTabCount kann die Anzahl der Registerkarten ermittelt werden, und mit getComponentAt kann die Komponente einer beliebigen Registerkarte ermittelt werden. Mit setComponentAt kann der Inhalt einer Registerkarte sogar nachträglich ausgetauscht werden: In einem Registerdialog ist immer genau eine der Karten selektiert. Mit getSelectedIndex kann deren Index ermittelt werden, mit getSelectedComponent sogar direkt auf ihren Inhalt zugegriffen werden. Die Methode setSelectedIndex erlaubt es, programmgesteuert eine beliebige Registerkarte auszuwählen: Registerkarten besitzen eine Reihe von Eigenschaften, die einzeln verändert werden können. Die wichtigste von ihnen ist die Möglichkeit, sie zu aktivieren oder zu deaktivieren. Nur aktivierte Karten können ausgewählt werden, deaktivierte dagegen nicht. Der Zugriff auf den Aktivierungsstatus erfolgt mit den Methoden setEnabledAt und isEnabledAt: Bei jeder Änderung der Selektion versendet ein JTabbedPane ein ChangeEvent an registrierte ChangeListener. Auf diese Weise ist es möglich, Programmcode zu schreiben, der beim Anwählen oder Verlassen einzelner Registerkarten ausgeführt wird. Eine der Anwendungen hierfür besteht darin, die Dialoge auf den Registerkarten erst dann zu erzeugen, wenn sie wirklich gebraucht werden. Dazu wird an alle Registerkarten zunächst ein leeres Panel übergeben und erst in der Methode stateChanged durch den eigentlichen Dialog ersetzt. Auf diese Weise spart das Programm beim Initialisieren des Registerdialogs Rechenzeit und Speicher, was sich vor allem bei komplexen Dialogen positiv bemerkbar macht. 1.3 Grafiken und Bilder 1.3.1 Grundlagen der Grafikausgabe 1.3.1.1 Beispiel PrimitiveRoutines /* PrimitiveRoutines.java */ import java.awt.*; import java.awt.event.*; public class PrimitiveRoutines extends Frame { int[] arx = {50,50,120,120,80,80,100,100,80,80}; int[] ary = {170,40,40,70,70,100,100,130,130,170}; public static void main(String[] args) { PrimitiveRoutines wnd = new PrimitiveRoutines(); 59 60 wnd.drawShapes(); } public PrimitiveRoutines() { super("PrimitiveRoutines"); addWindowListener(new WindowClosingAdapter(true)); setBackground(Color.lightGray); setSize(400,400); setVisible(true); } public void drawShapes() { Graphics g = getGraphics(); g.setClip(20,20,350,150); g.drawLine(20,40,20,300); g.drawOval(70,90,80,100); g.drawRect(170,90,80,100); g.drawPolygon(arx,ary,arx.length); g.fillRect(200,40,20,80); } } 1.3.1.2 Die Methode paint Die Ausgabe in ein Fenster erfolgt durch Überlagern der Methode paint, die immer dann aufgerufen wird, wenn das Fenster ganz oder teilweise neu gezeichnet werden muss. Dies ist beispielsweise dann der Fall, wenn das Fenster zum ersten Mal angezeigt wird oder durch Benutzeraktionen ein Teil des Fensters sichtbar wird, der bisher verdeckt war. paint bekommt beim Aufruf eine Instanz der Klasse Graphics übergeben. 1.3.1.3 Die Klasse Graphics Graphics ist Javas Implementierung eines Device-Kontexts (auch Grafikkontext genannt) und stellt somit die Abstraktion eines universellen Ausgabegeräts für Grafik und Schrift dar. Die Klasse bietet Methoden zur Erzeugung von Linien-, Füll- und Textelementen. Darüber hinaus verwaltet Graphics die Zeichenfarbe, in der alle Ausgaben erfolgen, und einen Font, der zur Ausgabe von Schrift verwendet wird. 1.3.1.4 Das grafische Koordinatensystem Die Ausgabe von Grafik basiert auf einem zweidimensionalen Koordinatensystem, dessen Ursprungspunkt (0,0) in der linken oberen Ecke liegt. Positive x-Werte erstrecken sich nach rechts, positive y-Werte nach unten. Die Maßeinheit entspricht einem Bildschirmpixel und ist damit geräteabhängig. In den meisten Fällen steht dem Programm nicht das gesamte Fenster zur Ausgabe zur Verfügung, sondern es gibt Randelemente wie Titelzeilen, Menüs oder Rahmen, die nicht überschrieben werden können. Mit der Methode getInsets() kann die Größe dieser Randelemente ermittelt werden. 60 61 1.3.1.5 Elementare Grafikroutinen Die Klasse Graphics stellt neben vielen anderen Funktionen auch eine Sammlung von linienbasierten Zeichenoperationen zur Verfügung. Diese sind zur Darstellung von einfachen Linien, Rechtecken oder Polygonen sowie von Kreisen, Ellipsen und Kreisabschnitten geeignet. 1.3.1.5.1 Linien public void drawLine(int x1, int y1, int x2, int y2) Zieht eine Linie von der Position (x1,y1) zur Position (x2,y2). Teile der Ausgabe, die außerhalb des darstellbaren Bereichs liegen, werden unterdrückt. Die Methode drawLine zeichnet Linien grundsätzlich mit einer Dicke von einem Pixel. 1.3.1.5.2 Rechteck public void drawRect(int x, int y, int width, int height) Zeichnet ein Rechteck der Breite width und der Höhe height, dessen linke obere Ecke an der Position (x,y) liegt. Eine größere Breite dehnt das Rechteck nach rechts aus, eine größere Höhe nach unten. Eine Variante von drawRect ist die Methode drawRoundRect: public void drawRoundRect( int x, int y, int width, int height, int arcWidth, int arcHeight ) Gegenüber drawRect sind hier die Parameter arcWidth und arcHeight dazugekommen. Sie bestimmen den horizontalen und vertikalen Radius des Ellipsenabschnitts, der zur Darstellung der runden »Ecke« verwendet wird. 1.3.1.5.3 Polygon Mit Hilfe der Methode drawPolygon() ist es möglich, Linienzüge zu zeichnen, bei denen das Ende eines Elements mit dem Anfang des jeweils nächsten verbunden ist: public void drawPolygon(int[] arx, int[] ary, int cnt) drawPolygon erwartet drei Parameter. Der erste ist ein Array mit einer Liste der xKoordinaten und der zweite ein Array mit einer Liste der y-Koordinaten. Beide Arrays müssen so synchronisiert sein, dass ein Paar von Werten an derselben Indexposition immer 61 62 auch ein Koordinatenpaar ergibt. Die Anzahl der gültigen Koordinatenpaare wird durch den dritten Parameter festgelegt. Mit Hilfe der Methode addPoint() kann ein Polygon um weitere Punkte erweitert werden. Schließlich kann ein fertiges Polygon an die Methode drawPolygon() übergeben werden : public void drawPolygon(Polygon p) 1.3.1.5.4 Kreis Die Graphics-Klasse von Java erlaubt sowohl das Zeichnen von Kreisen als auch von Ellipsen und Kreisabschnitten. Ein Kreis wird dabei als Verallgemeinerung einer Ellipse angesehen, und beide Objekte werden mit der Methode drawOval gezeichnet: public void drawOval(int x, int y, int width, int height) Anders als in anderen Grafiksystemen werden bei dieser Methode nicht der Mittelpunkt und der Radius des Kreises angegeben, sondern die übergebenen Parameter spezifizieren ein Rechteck der Größe width und heigth, dessen linke obere Ecke an der Position (x,y) liegt. Gezeichnet wird dann der größte Kreis, der vollständig in das Rechteck hineinpasst. 1.3.1.5.5 Kreisbogen Ein Kreisbogen ist ein zusammenhängender Abschnitt der Umfangslinie eines Kreises. Er kann mit der Methode drawArc() gezeichnet werden: public void drawArc( int x, int y, int width, int height, int startAngle, int arcAngle ) Die ersten vier Parameter bezeichnen dabei den Kreis bzw. die Ellipse so, wie dies auch bei drawOval der Fall war. Mit startAngle wird der Winkel angegeben, an dem mit dem Kreisabschnitt begonnen werden soll, und arcAngle gibt den zu überdeckenden Bereich an. Dabei bezeichnet ein Winkel von 0 Grad die 3-Uhr-Position, und positive Winkel werden entgegen dem Uhrzeigersinn gemessen. Als Einheit wird Grad verwendet und nicht das sonst übliche Bogenmaß. 1.3.1.6 Linien und Füllmodus Mit Ausnahme von drawLine() können alle vorgestellten Routinen entweder im Linien- oder im Füllmodus verwendet werden. Beginnt der Methodenname mit draw, erfolgt die Ausgabe im Linienmodus. Es wird lediglich der Umriss der jeweiligen Figur gezeichnet. Beginnt der Methodenname mit fill, erfolgt die Ausgabe im Füllmodus, und das Objekt wird mit ausgefüllter Fläche gezeichnet. 1.3.1.7 Kopieren und Löschen von Flächen Die Klasse Graphics stellt auch einige Methoden zum Bearbeiten bereits gezeichneter Flächen zur Verfügung. Diese erlauben es beispielsweise, einen rechteckigen Ausschnitt des Ausgabefensters zu löschen oder zu kopieren: Die Methode clearRect() löscht das angegebene Rechteck, indem sie den Bereich mit der aktuellen Hintergrundfarbe ausfüllt. Soll ein Teil des Fensters kopiert werden, kann dazu die Methode copyArea() verwendet werden. Die ersten vier Parameter bezeichnen in der üblichen Weise den zu kopierenden Ausschnitt. (dx,dy) gibt die Entfernung des Zielrechtecks in x- und y-Richtung an. copyArea kopiert das ausgewählte Rechteck also immer an die Position (x+dx, y+dy). 62 63 1.3.1.8 Die Clipping-Region Jeder Grafikkontext hat eine zugeordnete Clipping-Region, die dazu dient, die Ausgabe auf einen bestimmten Bereich einzugrenzen. So wird beispielsweise verhindert, dass wichtige Teile eines Fensters, wie z.B. der Rahmen oder die Menüzeile, von den Ausgabeoperationen des Programms überschrieben werden. Ein Fenster besitzt eine Clipping-Region, die an das übergebene Graphics-Objekt gebunden ist. Beim Aufruf der paint-Methode entspricht ihre Größe der zur Ausgabe zur Verfügung stehenden Fläche. Mit Hilfe der Methode clipRect kann die Clipping-Region nun sukzessive verkleinert werden: public void clipRect(int x, int y, int widh, int height) Ein Aufruf dieser Methode begrenzt die Clipping-Region auf die Schnittmenge zwischen dem angegebenen Rechteck und ihrer vorherigen Größe. Da eine Schnittmenge niemals größer werden kann als eine der Mengen, aus denen sie gebildet wurde, kann clipRect also lediglich dazu verwendet werden, die Clipping-Region zu verkleinern. Soll die Clipping-Region auf einen beliebigen Bereich innerhalb des aktuellen Fensters ausgedehnt werden (der dabei auch größer sein kann als die bisherige Clipping-Region), so kann dazu die Methode setClip() verwendet werden. Soll die Ausdehnung der aktuellen Clipping-Region ermittelt werden, so kann dazu eine der Methoden getClip oder getClipBounds verwendet werden. 1.3.2 Bitmaps und Animation 1.3.2.1 Bitmaps 1.3.2.1.1 Beispiel BitmapOhneRepaintPaintMechanismus public class Bitmap extends Frame { private Image img; public static void main(String[] args) { Bitmap wnd = new Bitmap(); } public Bitmap() { super("Bitmap"); setBackground(Color.lightGray); setSize(250,150); setVisible(true); //WindowListener addWindowListener(new WindowClosingAdapter(true)); img = getToolkit.getImage("…"); MediaTracker mt = new MediaTracker(this); Mt.addImage(img, 0); try{ mt.waitForAll(); } catch(InterruptedException e) {} Graphics g = getGraphics(); g.drawImage(img, 40, 40, this); } } 1.3.2.1.2 Laden einer Bitmap Das Laden erfolgt mit der Methode getImage(), die eine Instanz der Klasse Image zurückgibt. 63 64 getImage gibt es in verschiedenen Varianten, die sich dadurch unterscheiden, aus welcher Quelle sie die Bitmap-Daten laden. In einer Java-Applikation wird in der Regel die Methode getImage aus der Klasse Toolkit verwendet. Sie erwartet den Namen einer lokalen Datei als Parameter: 1.3.2.1.3 Die Klasse Toolkit Es gibt im AWT eine Klasse Toolkit, die als Hilfsklasse bei der Abbildung der portablen AWT-Eigenschaften dient. Toolkit ist standardmäßig abstrakt, wird aber von jeder AWT-Implementierung konkretisiert und den Anwendungen über die Klassenmethode getDefaultToolkit zur Verfügung gestellt. Das Toolkit für die aktuelle Umgebung kann mit der Methode getToolkit der Klasse Component beschafft werden. 1.3.2.1.4 Ausgeben einer Bitmap drawImage erwartet das anzuzeigende Image-Objekt und die Position (x,y), an der die linke obere Ecke der Bitmap plaziert werden soll. Das zusätzlich angegebene Objekt observer dient zur Übergabe eines ImageObserverObjektes, mit dem der Ladezustand der Bitmaps überwacht werden kann. Hier kann der this-Zeiger, also eine Referenz auf das Fensterobjekt, übergeben werden. 1.3.2.1.5 Die Klasse MediaTracker 1.3.2.1.5.1 Problem Beispiel BitmapOhneRepaintPaintMechanismus ohne MediaTracker ausführen ! Der Aufruf von getImage() lädt die Bitmap noch nicht, sondern bereitet das Laden nur vor. Der eigentliche Ladevorgang beginnt erst, wenn die Bitmap beim Aufruf von drawImage() tatsächlich benötigt wird. Die Methode drawImage() kehrt auch dann sofort zurück, wenn das Laden eines Bildes noch nicht abgeschlossen ist. Unfertige Bilder möchte man jedoch in der Regel nicht anzeigen sondern auf das vollständige Laden warten. Wird zum Beispiel die Größe der Bitmap benötigt, um sie korrekt auf dem Bildschirm anordnen oder skalieren zu können, muss das Programm warten, bis das Image vollständig erzeugt ist. 1.3.2.1.5.2 Lösung durch einen MediaTracker Die Klasse MediaTracker ermöglicht das Warten auf das Laden einer ganzen Serie von Bilder. Dazu wird zunächst eine Instanz der Klasse angelegt. Als Komponente wird der this-Zeiger des aktuellen Fensters übergeben. Anschließend werden durch Aufruf von addImage() alle Bilder, deren Ladevorgang überwacht werden soll, an den MediaTracker übergeben. Der zusätzlich übergebene Parameter id kann dazu verwendet werden, einen Namen zu vergeben, unter dem auf das Image zugegriffen werden kann. Zusätzlich bestimmt er die Reihenfolge, in der die Images geladen werden. Bitmaps mit kleineren Werten werden zuerst geladen. Der MediaTracker bietet Methoden, um den Ladezustand der Bilder zu überwachen : 64 65 waitForAll() wartet, bis alle Images vollständig geladen sind. 1.3.2.1.5.3 Beschreibungen aus der Online Hilfe track = verfolgen The MediaTracker class is a utility class to track the status of a number of media objects. Media objects could include audio clips as well as images, though currently only images are supported. To use a media tracker, create an instance of MediaTracker and call its addImage method for each image to be tracked. In addition, each image can be assigned a unique identifier. This identifier controls the priority order in which the images are fetched. It can also be used to identify unique subsets of the images that can be waited on independently. Images with a lower ID are loaded in preference to those with a higher ID number. waitForId(int id) throws InterruptedException Starts loading all images tracked by this media tracker with the specified identifier. This method waits until all the images with the specified identifier have finished loading. If there is an error while loading or scaling an image, then that image is considered to have finished loading. Use the isErrorAny and isErrorID methods to check for errors. id - the identifier of the images to check. die weitere Abarbeitung stoppt also, bis die gekennzeichneten Media-Objekte vollständig geladen sind public int statusID(int id, boolean load) Calculates and returns the bitwise inclusive OR of the status of all media with the specified identifier that are tracked by this media tracker. Possible flags defined by the MediaTracker class are LOADING, ABORTED, ERRORED, and COMPLETE. An image that hasn't started loading has zero as its status. If the value of load is true, then this method starts loading any images that are not yet being loaded. diese Methode liefert also den aktuellen Ladezustand, bei erfolgtem Laden ist das MediaTracker.COMPLETE public int statusAll(boolean load) Calculates and returns the bitwise inclusive OR of the status of all media that are tracked by this media tracker. diese Methode berechnet also eine Zusammenfassung der Zustände aller zu überwachenden Objekte, im Fehlerfall MediaTracker.ERRORED. drawImage public abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) Draws as much of the specified image as is currently available. The image is drawn with its top-left corner at (x, y) in this graphics context's coordinate space. Transparent pixels are drawn in the specified background color. This operation is equivalent to filling a rectangle of the width and height of the specified image with the given color and then drawing the image on top of it, but possibly more efficient. This method returns immediately in all cases, even if the complete image has not yet been loaded, and it has not been dithered and converted for the current output device. If the image has not yet been completely loaded, then drawImage returns false. As more of the image becomes available, the process that draws the image notifies the specified image observer. 65 66 interface ImageObserver An asynchronous update interface for receiving notifications about Image information as the Image is constructed. 1.3.2.1.6 Verbleibendes Problem Fährt man im Beispiel BitmapOhneRepaintPaintMechanismus z.B. mit dem DOS Fenster über das Bitmap, so werden die verdeckten Bitmap Teile nicht wieder neu dargestellt. Das Bitmap wir zerstört. Schiebt man das Bitmap aus dem Bildschirm oder bringt man die Applikation in den Hintergrund, ist das das Bitmap nicht mehr sichtbar. 1.3.2.1.7 Beispiel Bitmap (Mit repaint-paint Mechanismus) Abhilfe bietet der repaint-paint Mechanismus public class Bitmap extends Frame { private Image img; public static void main(String[] args) { Bitmap wnd = new Bitmap(); } public Bitmap() { super("Bitmap"); setBackground(Color.lightGray); setSize(250,150); setVisible(true); //WindowListener addWindowListener(new WindowClosingAdapter(true)); //Bild laden img = getToolkit().getImage("duke.gif"); MediaTracker mt = new MediaTracker(this); mt.addImage(img, 0); try { //Warten, bis das Image vollständig geladen ist, mt.waitForAll(); } catch (InterruptedException e) { //nothing } repaint(); } public void paint(Graphics){ if (img != Null) { g.drawImage(img, 40, 40, this); } } } 1.3.2.1.8 Der repaint() – paint() Mechanismus 1.3.2.1.8.1 Die Methode paint() Die Ausgabe in ein Fenster erfolgt durch die Methode paint(), die immer dann aufgerufen wird, wenn das Fenster ganz oder teilweise neu gezeichnet werden muss. Dies ist beispielsweise dann der Fall, wenn das Fenster zum ersten Mal angezeigt wird oder durch Benutzeraktionen ein Teil des Fensters sichtbar wird, der bisher verdeckt war. paint bekommt beim Aufruf eine Instanz der Klasse Graphics übergeben. 1.3.2.1.8.2 Die Methode repaint() 66 67 repaint() bewirkt den asynchronen Aufruf der update() Methode. Die Ausführung von update()ist also die Folge des Aufrufs der Methode repaint(). Mit der argumentlosen Variante überlässt der Programmierer die Reaktionszeit dem Laufzeitsystem. Nach einer gewissen Zeit, unter Umständen nach der Zusammenfassung mehrerer repaint()-Anforderungen, löst es update() aus. Dabei ist im argumentlosen Fall die gesamte Fläche zur Gestaltung freigegeben. 1.3.2.1.8.3 Die Methode update() Die Methode update() löscht die wirksame Fläche und ruft dann paint() mit demselben Graphics-Objekt auf. Dort soll der Programmierer die Fläche neu beschreiben. 1.3.2.1.8.4 Anmerkungen Die Methoden update() und paint() erhalten als Übergabeparameter ein Objekt der Klasse java.awt.Graphics. Die Methoden paint() und repaint() erbt die Klasse Frame von Component. Der Programmierer hat durch die anderen repaint()-Varianten zwei Mittel, die Wirkung von update() zu steuern. Er kann die Reaktionszeit einstellen, d.h. den Zeitraum, nach dem update() spätestens aufzurufen ist, und die wirksame Fläche einschränken. Achtung : Bei Einschränkung der wirksamen Fläche führt die Zusammenfassung mehrerer repaint()-Anforderungen durch das Laufzeitsystem zu einem möglicherweise ungewollten Effekt: Die wirksame Fläche des Graphics-Objekts in update() ist dann als das kleinste Rechteck definiert, das alle einzelnen wirksamen Flächen enthält (union rect). Die so definierte Fläche umfasst im Allgemeinen Gebiete, für die ursprünglich keine Aktualisierung vorgesehen war. 1.3.2.1.8.5 1.3.2.1.8.5.1 Unterschiede zwischen AWT und Swing AWT java.awt.Component public void repaint(long tm, int x, int y, int width, int height) Repaints the specified rectangle of this component within tm milliseconds. This method causes a call to this component's update method. public void update(Graphics g) Updates this component. The AWT calls the update method in response to a call to repaint. The appearance of the component on the screen has not changed since the last call to update or paint. You can assume that the background is not cleared. The updatemethod of Component does the following: · Clears this component by filling it with the background color. 67 68 · · Sets the color of the graphics context to be the foreground color of this component. Calls this component's paint method to completely redraw this component. public void paint(Graphics g) Paints this component. This method is called when the contents of the component should be painted in response to the component first being shown or damage needing repair. The clip rectangle in the Graphics parameter will be set to the area which needs to be painted. 1.3.2.1.8.5.2 Swing com.sun.java.swing.JComponent public void repaint(long tm, int x, int y, int width, int height) Adds the specified region to the dirty region list if the component is showing. The component will be repainted after all of the currently pending events have been dispatched. public void update(Graphics g) Calls paint(g). Doesn't clear the background. Code in JFrame /** * Just calls <code>paint(g)</code>. This method was overridden to * prevent an unneccessary call to clear the background. * * @param g the Graphics context in which to paint */ public void update(Graphics g) { paint(g); } Achtung ! Die update() Funktion von Swing löscht keinen Hintergrund. Achtung ! Der Hilfe Text enthält keinen Hinweis darauf, dass die update() Funktion im Zusammenhang mit repaint() überhaupt aufgerufen wird. public void paint(Graphics g) This method is invoked by Swing to draw components. Applications should not invoke paint directly, but should instead use the repaint method to schedule the component for redrawing. This method actually delegates the work of painting to three protected methods: paintComponent, paintBorder, and paintChildren. They're called in the order listed to ensure that children appear on top of component itself. Generally speaking, the component and its children should not paint in the insets area allocated to the border. Subclasses can just override this method, as always. A subclass that just wants to specialize the UI (look and feel) delegates paint method should just override paintComponent. Beispiel vgl. Grafik/Animation/RepaintPaintMechanismusFuerJPanel 1.3.2.2 1.3.2.2.1 Animation Beispiel BitmapAnimation 68 69 /* BitmapAnimation.java */ import java.awt.*; import java.awt.event.*; public class BitmapAnimation extends Frame { Image[] arImg; int actimage = -1; public static void main(String[] args) { BitmapAnimation wnd = new BitmapAnimation(); wnd.setSize(200,150); wnd.setVisible(true); wnd.startAnimation(); } public BitmapAnimation() { super("Bitmap-Folge"); addWindowListener(new WindowClosingAdapter(true)); } public void startAnimation() { //Bilder laden arImg = new Image[5]; MediaTracker mt = new MediaTracker(this); Toolkit tk = getToolkit(); for (int i = 0; i <= 4; ++i) { arImg[i] = tk.getImage("images/juggler"+i+".gif"); mt.addImage(arImg[i], 0); } try { mt.waitForAll(); } catch (InterruptedException e) { //nothing } //Animation beginnen actimage = 0; while (true) { repaint(); actimage = (actimage + 1) % 4; try { Thread.sleep(10); } catch (InterruptedException e) { //nichts } } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { if (actimage < 0) { g.drawString("Lade Bitmap !",10,50); } else { g.drawImage(arImg[actimage],10,30,this); } } } 1.3.2.2.2 Konzept Das Darstellen einer Animation auf dem Bildschirm ist im Prinzip nichts Anderes als die schnell aufeinanderfolgende Anzeige einer Sequenz von Einzelbildern. Die Bildfolge erscheint dem menschlichen Auge aufgrund seiner Trägheit als zusammenhängende Bewegung. 69 70 1.3.2.2.3 Beispiel FlackerndesImageApplet public class PartialRemovingImageApplet extends Applet implements MouseMotionListener { private Image image = null; private MediaTracker tracker = null; private int x = 0, y = 0, oldx = 0, oldy = 0, w = 100, h = 100; public void init() { tracker = new MediaTracker( this ); try { image = getImage( new URL( getDocumentBase(), "Willi.gif" )); tracker.addImage( image, 0 ); tracker.waitForID( 0 ); h = image.getHeight( this ); w = image.getWidth( this ); addMouseMotionListener( this ); } catch ( MalformedURLException me ) { System.err.println( "nicht gefunden" ); } catch ( InterruptedException ie ) { System.err.println( "Fehler beim Laden" ); } } public void mouseMoved( MouseEvent mouseEvent ) {} public void mouseDragged( MouseEvent mouseEvent ) { this.x = mouseEvent.getX(); this.y = mouseEvent.getY(); repaint(); } public void paint( Graphics g ) { if (( tracker.statusID( 0, false ) & MediaTracker.COMPLETE ) != 0 ) { g.clearRect(0, 0, getWidth(), getHeight()); g.drawImage( image, x, y, this ); } g.drawRect( x, y, w, h ); } // public void update( Graphics g ) { super.update(g); paint( g ); } /* Code der Basisklassen Funktion public void update(Graphics g) { if ((! (peer instanceof java.awt.peer.LightweightPeer)) && (! (this instanceof Label)) && (! (this instanceof TextField))) { g.setColor(getBackground()); g.fillRect(0, 0, width, height); g.setColor(getForeground()); } paint(g); } */ } 1.3.2.2.4 Reduktion des Bildschirmflackerns Die entwickelte Animation zeigt ein ausgeprägtes Flackern. Der Grund für dieses Flackern liegt darin, dass vor jedem Aufruf von paint() zunächst das Fenster gelöscht wird und dadurch unmittelbar vor der Ausgabe des nächsten Bildes ganz kurz ein vollständig leerer Hintergrund erscheint. Leider besteht die Lösung für dieses Problem nicht einfach darin, das Löschen zu unterdrücken. Bei einer animierten Bewegung beispielsweise ist es erforderlich, all die Bestandteile des vorigen Bildes zu löschen, die im aktuellen Bild nicht mehr oder an einer anderen Stelle angezeigt werden. Auch wenn paint deshalb aufgerufen wird, weil ein bisher verdeckter Bildausschnitt wieder sichtbar wird, muss natürlich der entsprechende Bildausschnitt zunächst gelöscht werden, 70 71 um die Bestandteile des anderen Fensters zu entfernen. Im Grunde ist es also eine ganz vernünftige Vorgehensweise, das Fenster vor jedem Aufruf von paint zu löschen. Das Flackern kann nun auf unterschiedliche Weise unterdrückt werden. Die drei gebräuchlichsten Methoden sind folgende: den Bildschirm gar nicht zu löschen (Dies ist nur selten möglich !) nur den wirklich benötigten Teil des Bildschirms zu löschen das Verfahren der Doppelpufferung anzuwenden Jedes dieser Verfahren hat Vor- und Nachteile und kann in verschiedenen Situationen unterschiedlich gut angewendet werden. 1.3.2.2.5 Beispiel PartialRemovingImageApplet PartialRemovingImageApplet // PartialRemovingImageApplet.java public class PartialRemovingImageApplet extends Applet implements MouseMotionListener { private Image image = null; private MediaTracker tracker = null; private int x = 0, y = 0, oldx = 0, oldy = 0, w = 100, h = 100; public void init() { tracker = new MediaTracker( this ); try { image = getImage( new URL( getDocumentBase(), "Willi.gif" )); tracker.addImage( image, 0 ); tracker.waitForID( 0 ); h = image.getHeight( this ); w = image.getWidth( this ); addMouseMotionListener( this ); } catch ( MalformedURLException me ) { System.err.println( "nicht gefunden" ); } catch ( InterruptedException ie ) { System.err.println( "Fehler beim Laden" ); } } public void mouseMoved( MouseEvent mouseEvent ) {} public void mouseDragged( MouseEvent mouseEvent ) { this.x = mouseEvent.getX(); this.y = mouseEvent.getY(); repaint(); } public void update( Graphics g ) { paint( g ); } public void paint( Graphics g ) { int int int int dx dw dy dh = = = = (( x < oldx ( Math.abs( (( y < oldy ( Math.abs( ) x ) y && ( x - oldx && ( y - oldy if (( tracker.statusID( 0, g.drawImage( image, g.drawRect( x, y, w, h ); g.clearRect( dx, oldy, dw, g.clearRect( oldx, dy, w + oldx = x; oldy = y; + ) + ) w < h < > w > h oldx )) ? x + ) ? Math.abs( oldy )) ? y + ) ? Math.abs( w x h y + + - 1 : oldx; oldx ) : w + 1; 1 : oldy; oldy ) : h + 1; false ) & MediaTracker.COMPLETE ) != 0 ) x, y, this ); h + 1 ); 1, dh ); } } update ruft nur paint() auf, ohne die Fläche vorher zu löschen. 71 72 Die Methode paint()löscht alle Flächen, die beim letzten Aufruf vom Bild aber jetzt nicht mehr eingenommen wurden. URL getDocumentBase() Gets the document URL. This is the URL of the document in which the applet is embedded. Class URL represents a Uniform Resource Locator, a pointer to a "resource" on the World Wide Web. 1.3.2.2.6 Selektives Löschen Selektives Löschen"Der Programmierer löscht dabei statt der gesamten Fläche nur Gebiete, bei denen durch das Bewegen eines Bildobjekts der Hintergrund wieder sichtbar wird. In der Regel wird update() hier einfach paint() aufrufen. paint() zeichnet das zu bewegende Bildobjekt an der neuen Position und füllt die Differenzfläche aus alter und neuer Position mit der Hintergrundfarbe bzw. dem Hintergrundbild. 1.3.2.2.7 Beispiel DoubleBufferingImageApplet DoubleBufferingImageApplet DoubleBufferingImageApplet.java public class DoubleBufferingImageApplet extends Applet implements MouseMotionListener { private private private private private private Image image = null; Image offscreenImage = null; Graphics offscreenGraphics = null; Dimension appletDimension = null; MediaTracker tracker = null; int x = 0, y = 0, w = 100, h = 100; public void init() { tracker = new MediaTracker( this ); try { image = getImage( new URL( getDocumentBase(), "Willi.gif" )); tracker.addImage( image, 0 ); tracker.waitForID( 0 ); h = image.getHeight( this ); w = image.getWidth( this ); addMouseMotionListener( this ); appletDimension = getSize(); offscreenImage = createImage( appletDimension.width, appletDimension.height ); offscreenGraphics = offscreenImage.getGraphics(); offscreenGraphics.drawImage( image, x, y, this ); offscreenGraphics.drawRect( x, y, w, h ); } catch ( MalformedURLException me ) { System.err.println( "nicht gefunden" ); } catch ( InterruptedException ie ) { System.err.println( "Fehler beim Laden" ); } } public void mouseMoved( MouseEvent mouseEvent ) {} public void mouseDragged( MouseEvent mouseEvent ) { this.x = mouseEvent.getX(); this.y = mouseEvent.getY(); repaint(); } public void update(Graphics g) { paint(g); paint(offscreenGraphics); g.drawImage(offscreenImage, 0, 0, this); } public void paint( Graphics g ) { g.clearRect( 0, 0, appletDimension.width, appletDimension.height ); g.drawImage( image, x, y, this ); g.drawRect( x, y, w, h ); } } 72 73 1.3.2.2.8 Doppelpufferung Beim Double Buffering benutzt der Programmierer ein zweites Graphics-Objekt zur Anordnung der Bildobjekte. Das Fensterobjekt beschafft sich durch Aufruf von createImage() ein Offscreen-Image und speichert es in einer Instanzvariablen. Durch Aufruf von getGraphics() wird ein Grafikkontext zu diesem Image beschafft. Alle Bildschirmausgaben (inklusive Löschen des Bildschirms) gehen zunächst auf den Offscreen-Grafikkontext. Wenn alle Ausgabeoperationen abgeschlossen sind, wird das Offscreen-Image mit drawImage in das Ausgabefenster kopiert. Durch diese Vorgehensweise wird erreicht, dass das Bild komplett aufgebaut ist, bevor es angezeigt wird. Da beim anschließenden Kopieren die neuen Pixel direkt über die alten kopiert werden, erscheinen dem Betrachter nur die Teile des Bildes verändert, die auch tatsächlich geändert wurden. Ein Flackern, das entsteht, weil Flächen für einen kurzen Zeitraum gelöscht und dann wieder gefüllt werden, kann nicht mehr auftreten. Das DoubleBuffeering ist für Swing JComponents bereits vorgesehen : JComponent.setDoubleBuffered(boolean aFlag) Set whether the receiving component should use a buffer to paint. If set to true, all the drawing from this component will be done in an offscreen painting buffer. The offscreen painting buffer will the be copied onto the screen. Swing's painting system always use a maximum of one double buffer. If a Component is buffered and one of its ancestor is also buffered, the ancestor buffer will be used. Achtung : JFrame ist nicht von JComponent abgeleitet ! 1.3.2.2.9 Beispiel ExclusiveOrImageApplet ExclusiveOrImageApplet.java public class ExclusiveOrImageApplet extends PartialRemovingImageApplet { private int xLineStart = 0, yLineStart = 0, xLineEnd = 0, xLineEndOld = 0, yLineEnd = 0, yLineEndOld = 0; private boolean line = false; public void mouseMoved( MouseEvent mouseEvent ) { xLineEnd = mouseEvent.getX(); yLineEnd = mouseEvent.getY(); line = true; repaint(); } public void mouseDragged( MouseEvent mouseEvent ) { xLineStart = mouseEvent.getX(); yLineStart = mouseEvent.getY(); xLineEnd = xLineStart; yLineEnd = yLineStart; line = false; super.mouseDragged( mouseEvent ); repaint(); } public void paint( Graphics g ) { if ( line ) { g.setXORMode(color.white); g.drawLine( xLineStart, yLineStart, xLineEndOld, yLineEndOld ); g.drawLine( xLineStart, yLineStart, xLineEnd, yLineEnd ); } else { g.setPaintMode(); super.paint( g ); } xLineEndOld = xLineEnd; yLineEndOld = yLineEnd; 73 74 } } 1.3.2.2.10 Löschen mit Exclusiv-Oder Wie löscht man eine gezeichnete Linie wieder? Natürlich indem man sie mit der Hintergrundfarbe übermalt. Wenn aber die Linie über ein Bild gelegt war, dann sieht man nun statt der ursprünglichen Linie eine Linie in der Hintergrundfarbe über dem Bild. Dieses Problem löst das Malen im Exclusiv-Oder-Modus. Das folgende Beispiel zeigt dessen Anwendung. Beispiel für XOR weiß = 0 schwarz = 1 schwarz auf schwarz = 1 XOR 1 = 0 = weiß schwarz auf weiß = 1 XOR 0 = 1 = schwarz Beim erstenmal erscheint eine weiße Linie auf schwarzem Hintergrund, beim nächstenmal erscheint die schwarze Linie, d.h. der Ausgangszustand ist wieder erreicht. 1.4 Objektserialiserung 1.4.1 Serialisierung von einfachen Objekten 1.4.1.1 Beispiel Einfache Serialisierung SimpleSerializing /* SimpleSerializing.java */ import java.io.*; import java.util.*; public class SimpleSerializing { public static void main(String[] args)throws java.io.IOException { try { FileOutputStream fs = new FileOutputStream("Test2ser"); ObjectOutputStream os = new ObjectOutputStream(fs); os.write(123); os.writeObject("Hallo"); os.writeObject(new Time(10, 30)); os.writeObject(new Time(11, 25)); os.close(); } catch (IOException e) { System.err.println(e.toString()); } System.out.println("Fertig mit Serialiseren ! Bitte Taste druecken !"); try { FileInputStream fs = new FileInputStream("test2.ser"); ObjectInputStream is = new ObjectInputStream(fs); System.out.println("" + is.readInt()); System.out.println((String)is.readObject()); Time time = (Time)is.readObject(); System.out.println(time.toString()); time = (Time)is.readObject(); System.out.println(time.toString()); is.close(); } catch (ClassNotFoundException e) { System.err.println(e.toString()); } catch (IOException e) { System.err.println(e.toString()); } System.out.println("Fertig mit Deserialiseren ! Bitte Taste druecken !"); System.in.read(); 74 75 } } 1.4.1.2 Begriffsbestimmung Die Objektserialiserung ist die Konvertierung von Java-Objekten in einen Strom von Bytes. Dazu werden die Instanzvariablen in einer definierten Reihenfolge z.B. in eine Datei oder eine Netzwerkverbindung geschrieben. Serialisierung wird häufig mit dem Begriff Persistenz gleichgesetzt, vor allem in objektorientierten Programmiersprachen. Das ist nur bedingt richtig, denn Persistenz bezeichnet genaugenommen das dauerhafte Speichern von Daten auf einem externen Datenträger, so dass sie auch nach dem Beenden des Programms erhalten bleiben. Obwohl die persistente Speicherung von Objekten sicherlich eine der Hauptanwendungen der Serialisierung ist, ist sie nicht ihre einzige. 1.4.1.3 Schreiben von Objekten 1.4.1.3.1 Die Klasse ObjectOutputStream Eine zentrale Rolle im Serialiserungsprozess spielt die Klasse ObjectOutputStream. ObjectOutputStream besitzt einen Konstruktor, der einen OutputStream als Argument erwartet. Der an den Konstruktor übergebene OutputStream dient als Ziel der Ausgabe. Hier kann ein beliebiges Objekt der Klasse OutputStream oder einer daraus abgeleiteten Klasse übergeben werden. Typischerweise wird ein FileOutputStream verwendet, um die serialisierten Daten in eine Datei zu schreiben. 1.4.1.3.2 Methoden von ObjectOutputStream ObjectOutputStream besitzt sowohl Methoden, um primitive Typen zu serialisieren, als auch die wichtige Methode writeObject, mit der ein komplettes Objekt serialisiert werden kann: public final void writeObject(Object obj) public void writeBoolean(boolean data) public void writeByte(int data) public void writeShort(int data) public void writeChar(int data) public void writeInt(int data) public void writeLong(long data) public void writeFloat(float data) public void writeDouble(double data) public void writeBytes(String data) public void writeChars(String data) public void writeUTF(String data) 1.4.1.3.3 Konzept Aufwendig ist das Serialisieren von Objekten vor allem aus zwei Gründen: Erstens muss zur Laufzeit ermittelt werden, welche Membervariablen das zu serialisierende Objekt besitzt (bzw. geerbt hat) und welche von ihnen serialisiert werden sollen. Dazu wird das Reflection-API verwendet. Zweitens kann eine Membervariable natürlich selbst ein Objekt sein, das seinerseits serialisiert werden muss. Insbesondere muss writeObject dabei korrekt mit zyklischen Verweisen umgehen und dafür sorgen, dass Objektreferenzen erhalten bleiben. WriteObject schreibt folgende Daten in den OutputStream: 75 76 Die Klasse des als Argument übergebenen Objekts. Die Signatur der Klasse Alle nicht-statischen, nicht-transienten Membervariablen des übergebenen Objekts inkl. der aus allen Vaterklassen geerbten Membervariablen. Die Methode writeObject durchsucht das übergebene Objekt nach Membervariablen und überprüft deren Attribute. Ist eine Membervariable vom Typ static, wird es nicht serialisiert, denn es gehört nicht zum Objekt, sondern zur Klasse des Objekts. Weiterhin werden alle Membervariablen ignoriert, die mit dem Schlüsselwort transient deklariert wurden. Auf diese Weise kann das Objekt Membervariablen definieren, die aufgrund ihrer Natur nicht serialisiert werden sollen oder dürfen. Die Sichtbarkeit einer Membervariable hat keinen Einfluss darauf, ob sie von writeObject serialisiert wird oder nicht. 1.4.1.3.4 Das Interface Serializable Ein Objekt kann nur dann mit writeObject serialisiert werden, wenn es das Interface Serializable implementiert. Zwar definiert Serializable keine Methoden, writeObject testet jedoch, ob das zu serialisierende Objekt dieses Interface implementiert. Ist das nicht der Fall, wird eine Ausnahme des Typs NotSerializableException ausgelöst. 1.4.1.4 Lesen von Objekten Nachdem ein Objekt serialisiert wurde, kann es mit Hilfe der Klasse ObjectInputStream wieder rekonstruiert werden. Analog zu ObjectOutputStream gibt es Methoden zum Wiedereinlesen von primitiven Typen und eine Methode readObject, mit der ein serialisiertes Objekt wieder hergestellt werden kann. Das Deserialisieren eines Objektes kann man sich stark vereinfacht aus den folgenden beiden Schritten bestehend vorstellen: Zunächst wird ein neues Objekt des zu deserialisierenden Typs angelegt, und die Membervariablen werden mit Default-Werten vorbelegt. Zudem wird der Default-Konstruktor der ersten nicht-serialisierbaren Superklasse aufgerufen. Anschließend werden die serialisierten Daten gelesen und den entprechenden Membervariablen des angelegten Objekts zugewiesen. Das erzeugte Objekt hat anschließend dieselbe Struktur und denselben Zustand, den das serialisierte Objekt zuvor hatte (abgesehen von den nicht serialisierten Membervariablen des Typs static oder transient). Da der Rückgabewert von readObject vom Typ Object ist, muss das erzeugte Objekt in den tatsächlichen Typ (oder eine seiner Oberklassen) umgewandelt werden. 1.4.1.5 Anmerkungen Es ist wichtig zu verstehen, dass beim Deserialisieren nicht der Konstruktor des erzeugten Objekts aufgerufen wird. Lediglich bei einer serialisierbaren Klasse, die in ihrer Vererbungshierarchie Superklassen hat, die nicht das Interface Serializable implementieren, wird der parameterlose Konstruktor der nächsthöheren nicht-serialisierbaren Vaterklasse aufgerufen. Da die aus der nicht-serialisierbaren Vaterklasse geerbten Membervariablen nicht serialisiert werden, soll auf diese Weise sichergestellt sein, dass sie wenigstens sinnvoll initialisiert werden. Auch eventuell vorhandene Initialisierungen einzelner Membervariablen werden nicht ausgeführt. 76 77 Wir könnten beispielsweise die Time-Klasse um eine Membervariable seconds erweitern: private transient int seconds = 11; Dann wäre zwar bei allen mit new konstruierten Objekten der Sekundenwert mit 11 vorbelegt. Bei Objekten, die durch Deserialisieren erzeugt wurden, bleibt er aber 0 (das ist der Standardwert eines int), denn der Initialisierungscode wird in diesem Fall nicht ausgeführt. Beim Deserialisieren von Objekten können einige Fehler passieren. Damit ein Aufruf von readObject erfolgreich ist, müssen mehrere Kriterien erfüllt sein: Das nächste Element des Eingabestreams ist tatsächlich ein Objekt (kein primitiver Typ). Das Objekt muss sich vollständig und fehlerfrei aus der Eingabedatei lesen lassen. Es muss eine Konvertierung auf den gewünschten Typ erlauben, also entweder zu derselben oder einer daraus abgeleiteten Klasse gehören. Der Bytecode für die Klasse des zu deserialisierenden Objekts muss vorhanden sein. Er wird beim Serialisieren nicht mitgespeichert, sondern muss dem Empfängerprogramm wie üblich als kompilierter Bytecode zur Verfügung stehen. 1.4.1.6 Auszug aus der Online Hilfe interface Serializable Serializability of a class is enabled by the class implementing the java.io.Serializable interface. Classes that do not implement this interface will not have any of their state serialized or deserialized. All subtypes of a serializable class are themselves serializable. The serialization interface has no methods or fields and serves only to identify the semantics of being serializable. To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream. When traversing a graph, an object may be encountered that does not support the Serializable interface. In this case the NotSerializableException will be thrown and will identify the class of the non-serializable object. Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures: private void writeObject(java.io.ObjectOutputStream out) private void readObject(java.io.ObjectInputStream in) The writeObject method is responsible for writing the state of the object for its particular class so that the corresponding readObject method can restore it. The readObject method is responsible for reading from the stream and restoring the classes fields. 1.4.1.7 Nicht-serialisierte Membervariablen Mitunter besitzt eine Klasse Membervariablen, die nicht serialisiert werden sollen. Typische Beispiele sind Variablen, deren Wert sich während des Programmlaufs dynamisch ergibt, oder solche, die nur der temporären Kommunikation zwischen zwei oder mehr Methoden dienen. Auch Daten, die nur im Kontext der laufenden Anwendung Sinn machen, wie 77 78 beispielsweise Filehandles, Sockets oder GUI-Ressourcen, sollten nicht serialisiert werden; sie verfallen mit dem Ende des Programms. Membervariablen, die nicht serialisiert werden sollen, können mit dem Attribut transient versehen werden. Dadurch werden sie beim Schreiben des Objekts mit writeObject ignoriert und gelangen nicht in die Ausgabedatei. Beim Deserialisieren werden die transienten Objekte lediglich mit dem typspezifischen Standardwert belegt. 1.4.2 1.4.2.1 Serialiserung von Graphen Beispiel Graph Serialiserung Familie GraphSerializing: Heinrich Lina Peter Jens Renate Christian Bild Familienvererbungsbaum /* GraphSerializing.java */ import java.io.*; import java.util.*; public class GraphSerializing { public static void main(String[] args)throws java.io.IOException { //Erzeugen der Familie Person opa = new Person("Heinrich"); Person oma = new Person("Lina"); Person vater = new Person("Peter"); Person mutter = new Person("Renate"); Person kind1 = new Person("Jens"); Person kind2 = new Person("Christian"); vater.father = opa; vater.mother = oma; kind1.father = kind2.father = vater; kind1.mother = kind2.mother = mutter; //Serialisieren der Familie try { FileOutputStream fs = new FileOutputStream("test3.ser"); ObjectOutputStream os = new ObjectOutputStream(fs); os.writeObject(kind1); os.writeObject(kind2); os.close(); } catch (IOException e) { System.err.println(e.toString()); } //Rekonstruieren der Familie kind1 = kind2 = null; try { FileInputStream fs = new FileInputStream("test3.ser"); ObjectInputStream is = new ObjectInputStream(fs); kind1 = (Person)is.readObject(); kind2 = (Person)is.readObject(); //Überprüfen der Objekte System.out.println(kind1.name); System.out.println(kind2.name); System.out.println(kind1.father.name); System.out.println(kind1.mother.name); 78 79 System.out.println(kind2.father.name); System.out.println(kind2.mother.name); System.out.println(kind1.father.father.name); System.out.println(kind1.father.mother.name); //Name des Vaters ändern kind1.father.name = "Friedrich"; //Erneutes Überprüfen der Objekte System.out.println("---"); System.out.println(kind1.name); System.out.println(kind2.name); System.out.println(kind1.father.name); System.out.println(kind1.mother.name); System.out.println(kind2.father.name); System.out.println(kind2.mother.name); System.out.println(kind1.father.father.name); System.out.println(kind1.father.mother.name); is.close(); } catch (ClassNotFoundException e) { System.err.println(e.toString()); } catch (IOException e) { System.err.println(e.toString()); } System.in.read(); } } 1.4.2.2 Objektreferenzen 1.4.2.2.1 Problem Referenzen werden automatisch gesichert und rekonstruiert. Besitzt ein Objekt selbst Strings, Arrays oder andere Objekte als Membervariablen, so werden diese ebenso wie die primitiven Typen serialisiert und deserialisiert. Da eine Objektvariable lediglich einen Verweis auf das im Hauptspeicher allozierte Objekt darstellt, ist es wichtig, dass diese Verweise auch nach dem Serialisieren/Deserialisieren erhalten bleiben. Insbesondere darf ein Objekt auch dann nur einmal angelegt werden, wenn darauf von mehr als einer Variable verwiesen wird. Auch nach dem Deserialisieren darf das Objekt nur einmal vorhanden sein, und die verschiedenen Objektvariablen müssen auf dieses Objekt zeigen. 1.4.2.2.2 Lösung Der ObjectOutputStream hält eine Hashtabelle, in der alle bereits serialisierten Objekte verzeichnet werden. Bei jedem Aufruf von writeObject wird zunächst in der Tabelle nachgesehen, ob das Objekt bereits serialisiert wurde. Ist das der Fall, wird in der Ausgabedatei lediglich ein Verweis auf das Objekt gespeichert. Andernfalls wird das Objekt serialisiert und in der Hashtabelle eingetragen. Beim Deserialisieren eines Verweises wird dieser durch einen Objektverweis auf das zuvor deserialisierte Objekt ersetzt. Auf diese Weise werden Objekte nur einmal gespeichert, die Objektreferenzen werden konserviert, und das Problem von Endlosschleifen durch zyklische Referenzen ist ebenfalls gelöst. 1.4.2.2.3 Warnungen Einerseits kann es passieren, dass mehr Objekte als erwartet serialisiert werden. Insbesondere bei komplexen Objektbeziehungen kann es sein, dass an dem zu serialisierenden Objekt indirekt viele weitere Objekte hängen und beim Serialisieren wesentlich mehr Objekte gespeichert werden, als erwartet wurde. Das kostet unnötig Zeit und Speicher. Durch das Zwischenspeichern der bereits serialisierten Objekte in ObjectOutputStream werden viele Verweise auf Objekte gehalten, die sonst möglicherweise für das Programm unerreichbar wären. Da der Garbage Collector diese Objekte nicht freigibt, kann es beim Serialisieren einer großen Anzahl von Objekten zu Speicherproblemen kommen. Mit Hilfe 79 80 der Methode reset kann der ObjectOutputStream in den Anfangszustand versetzt werden; alle bereits bekannten Objektreferenzen werden »vergessen«. Wird ein bereits serialisiertes Objekt danach noch einmal gespeichert, wird kein Verweis, sondern das Objekt selbst noch einmal geschrieben. Wenn ein bereits serialisiertes Objekt verändert und anschließend erneut serialisiert wird, bleibt die Veränderung beim Deserialisieren unsichtbar, denn in der Ausgabedatei wird lediglich ein Verweis auf das Originalobjekt gespeichert. 1.4.3 Serialisierung von Collections 1.4.3.1 Beispiel ObjectStore Die folgende Klasse ObjectStore stellt ein Beispiel für einen persistenten Objektspeicher dar. /* StoreUser.java */ import java.io.*; import java.util.*; public class StoreUser { public static void main(String[] args)throws java.io.IOException { //Erzeugen und Speichern des Objektspeichers ObjectStore tos = new ObjectStore("OttoVersand"); tos.putObject("name", "Otto Versand Hamburg"); tos.putObject("besitzer", "Otto"); Vector products = new Vector(10); products.addElement("Hemd"); products.addElement("Hose"); products.addElement("Socken"); products.addElement("Schlips"); products.addElement("Zahnbuerste"); tos.putObject("produkte", products); try { tos.save(); } catch (IOException e) { System.err.println(e.toString()); } //Einlesen des Objektspeichers TrivialObjectStore tos2 = new TrivialObjectStore("OttoVersand"); try { tos2.load(); Enumeration names = tos2.getAllNames(); while (names.hasMoreElements()) { String name = (String)names.nextElement(); Object obj = tos2.getObject(name); System.out.print(name + ": "); System.out.println("Classname : " + obj.getClass().toString()); if (obj instanceof Vector) { Vector v = (Vector)obj; for (int i = 0; i < v.size(); i++) { System.out.println(" " + v.elementAt(i).toString()); } } else { System.out.println(" " + obj.toString()); } } } catch (IOException e) { System.err.println(e.toString()); } catch (ClassNotFoundException e) { System.err.println(e.toString()); } System.in.read(); } } /* ObjectStore.java */ import java.io.*; import java.util.*; 80 81 public class ObjectStore { private String fname; private Hashtable objects; public ObjectStore(String fname) { this.fname = fname; if (!fname.endsWith(".tos")) { this.fname += ".tos"; } this.objects = new Hashtable(50); } public void save() throws IOException { FileOutputStream fs = new FileOutputStream(fname); ObjectOutputStream os = new ObjectOutputStream(fs); os.writeObject(objects); os.close(); } public void load() throws ClassNotFoundException, IOException { FileInputStream fs = new FileInputStream(fname); ObjectInputStream is = new ObjectInputStream(fs); objects = (Hashtable)is.readObject(); is.close(); } public void putObject(String name, Object object) { objects.put(name, object); } public Object getObject(String name) { return objects.get(name); } public Enumeration getAllNames() { return objects.keys(); } } public class Hashtable This class implements a hashtable, which maps keys to values. Any non-null object can be used as a key or as a value. To successfully store and retrieve objects from a hashtable, the objects used as keys must implement the hashCode method and the equals method. get(Object key) Returns the value to which the specified key is mapped in this hashtable. keys() Returns an enumeration of the keys in this hashtable. put(Object key, Object value) Maps the specified key to the specified value in this hashtable. 1.4.3.2 Serialisierbare Standardklassen Neben selbstgeschriebenen Klassen sind auch viele der Standardklassen des JDK serialisierbar, insbesondere die meisten Collection-Klassen. Um beispielsweise alle Daten eines Vektors oder einer Hashtable persistent zu speichern, genügt ein einfaches Serialisieren. Voraussetzung ist allerdings, dass auch die Elemente der Collection serialisierbar sind, andernfalls gibt es eine NotSerializableException. Auch die Wrapperklassen zu den Basistypen sind standardmäßig serialisierbar und können damit problemlos als Objekte in serialisierbaren Collections verwendet werden. 1.4.4 Kopieren durch Serialisieren 81 82 1.4.4.1 Beispiel Kopieren durch Serialisierung /* SerializeCopy.java */ import java.io.*; import java.util.*; public class SerializeCopy { public static Object serializedCopyObject(Object o) throws IOException, ClassNotFoundException { //Serialisieren des Objekts ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream os = new ObjectOutputStream(out); os.writeObject(o); os.flush(); //Deserialisieren des Objekts ByteArrayInputStream in = new ByteArrayInputStream( out.toByteArray() ); ObjectInputStream is = new ObjectInputStream(in); Object ret = is.readObject(); is.close(); os.close(); return ret; } public static void main(String[] args) { try { //Erzeugen des Buchobjekts Book book = new Book(); book.author = "Peitgen, Heinz-Otto"; String[] s = {"Jürgens, Hartmut", "Saupe, Dietmar"}; book.coAuthors = s; book.title = "Bausteine des Chaos"; book.publisher = "rororo science"; book.pubyear = 1998; book.pages = 514; book.isbn = "3-499-60250-4"; book.reflist = new Vector(); book.reflist.addElement("The World of MC Escher"); book.reflist.addElement( "Die fraktale Geometrie der Natur" ); book.reflist.addElement("Gödel, Escher, Bach"); System.out.println(book.toString()); //Erzeugen und Verändern der Kopie Book copy = (Book)serializedCopyObject(book); copy.title += " - Fraktale"; copy.reflist.addElement("Fractal Creations"); //Ausgeben von Original und Kopie System.out.print(book.toString()); System.out.println("---"); System.out.print(copy.toString()); System.in.read(); } catch (IOException e) { System.err.println(e.toString()); } catch (ClassNotFoundException e) { System.err.println(e.toString()); } } } class Book implements Serializable { public String author; public String[] coAuthors; public String title; public String publisher; public int pubyear; public int pages; public String isbn; public Vector reflist; public String toString() 82 83 { String NL = System.getProperty("line.separator"); StringBuffer ret = new StringBuffer(200); ret.append(author + NL); for (int i = 0; i < coAuthors.length; ++i) { ret.append(coAuthors[i] + NL); } ret.append("\"" + title + "\"" + NL); ret.append(publisher + " " + pubyear + NL); ret.append(pages + " pages" + NL); ret.append(isbn + NL); Enumeration e = reflist.elements(); while (e.hasMoreElements()) { ret.append(" " + (String)e.nextElement() + NL); } return ret.toString(); } } 1.4.4.2 Serialisieren in Byte Array Soll ein komplexes Objekt kopiert werden, wird dazu üblicherweise das Interface Cloneable implementiert und eine Methode clone zur Verfügung gestellt, die den eigentlichen Kopiervorgang vornimmt. Sollte ein Objekt kopiert werden, das Cloneable nicht implementiert, blieb bisher nur der umständliche Weg über das manuelle Kopieren aller Membervariablen. Das ist insbesondere dann mühsam und fehlerträchtig, wenn das zu kopierende Objekt Unterobjekte enthält, die ihrerseits kopiert werden müssen (deep copy anstatt shallow copy). Der Schlüssel zum Kopieren von Objekten mit Hilfe der Serialisierung liegt darin, anstelle der üblichen dateibasierten Streamklassen solche zu verwenden, die ihre Daten im Hauptspeicher halten. Am besten sind dazu ByteArrayOutputStream und ByteArrayInputStream geeignet. Das Verfahren findet dort seine Grenzen, wo die zu kopierenden Objekte nicht serialisierbar sind oder nicht-serialisierbare Unterobjekte enthalten. Der Vorgang des Serialisierens/Deserialisierens ist langsamer als das direkte Kopieren der Objektattribute. 1.5 Exception Handling 1.5.1 Behandlung von Exceptions 1.5.1.1 Beispiel Simple Exception Achtung : Im Debug Modus laufen lassen (DOS Fenster wird sofort wieder geschlossen !). /* SimpleException.java */ public class SimpleException { public static void main(String[] args) { int i, base = 0; try { for (base = 10; base >= 2; --base) { i = Integer.parseInt("40",base); System.out.println("40 base "+base+" = "+i); } } catch (NumberFormatException e) { System.out.println("Fehler aufgetreten !"); System.out.println("Ursache: "+e.getMessage()); e.printStackTrace(); System.out.println("40 ist keine Zahl zur Basis "+base); } } } 83 84 1.5.1.2 Die try-catch-Anweisung Das Behandeln von Ausnahmen erfolgt mit Hilfe der try-catch-Anweisung: try { Anweisung; ... } catch (Ausnahmetyp x) { Anweisung; ... } Der try-Block enthält eine oder mehrere Anweisungen, bei deren Ausführung ein Fehler des Typs Ausnahmetyp auftreten kann. In diesem Fall wird die normale Programmausführung unterbrochen, und der Programmablauf fährt mit der ersten Anweisung nach der catchKlausel fort, die den passenden Ausnahmetyp deklariert hat. Hier kann nun Code untergebracht werden, der eine angemessene Reaktion auf den Fehler realisiert. 1.5.1.3 Das Fehlerobjekt Fehlerobjekte sind dabei Instanzen der Klasse Throwable oder einer ihrer Unterklassen. Sie werden vom Aufrufer der Ausnahme erzeugt und als Parameter an die catch-Klausel übergeben. Das Fehlerobjekt enthält Informationen über die Art des aufgetretenen Fehlers. So liefert beispielsweise die Methode getMessage einen Fehlertext (wenn dieser explizit gesetzt wurde), und printStackTrace druckt einen Auszug aus dem Laufzeit-Stack. Am einfachsten kann der Fehlertext mit der Methode toString der Klasse Throwable ausgegeben werden: 1.5.1.4 Die Fehlerklassen von Java Alle Laufzeitfehler in Java sind Unterklassen der Klasse Throwable. Throwable ist eine allgemeine Fehlerklasse, die im Wesentlichen eine Klartext-Fehlermeldung speichern und einen Auszug des Laufzeit-Stacks ausgeben kann. Unterhalb von Throwable befinden sich zwei große Vererbungshierarchien: Die Klasse Error ist Superklasse aller schwerwiegenden Fehler. Diese werden hauptsächlich durch Probleme in der virtuellen Java-Maschine ausgelöst. Fehler der Klasse Error sollten in der Regel nicht abgefangen werden, sondern (durch den StandardFehlerhandler) nach einer entsprechenden Meldung zum Abbruch des Programms führen. Alle Fehler, die möglicherweise für die Anwendung selbst von Interesse sind, befinden sich in der Klasse Exception oder einer ihrer Unterklassen. Ein Fehler dieser Art signalisiert einen abnormen Zustand, der vom Programm abgefangen und behandelt werden kann. Viele Pakete der Java-Klassenbibliothek definieren ihre eigenen Fehlerklassen. So gibt es spezielle Fehlerklassen für die Dateiein- und -ausgabe, die Netzwerkkommunikation oder den Zugriff auf Arrays. Die Klasse Throwable public class Throwable extends Object Konstruktoren public Throwable() public Throwable( String message ) // Nachricht, die man in der catch-Anweisung // abfragen kann. Methoden public String getMessage() // Nachricht, mit der das Objekt erzeugt wurde public String toString() // Zeichenkettenrepräsentation public void printStackTrace() // Ausgabe des Stacks auf die Standardausgabe 84 85 public // public // void printStackTrace( PrintStream s ) Ausgabe des Stacks nach s Throwable fillInStackTrace() Hinzufügen der Ausnahme zum Trace Die Ausnahmen des Paketes java.lang public class ArithmeticException extends RuntimeException // allgemeine Arithmetik-Ausnahme public class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException // unerlaubter Feldzugriff public class ArrayStoreException extends RuntimeException // Ausnahme beim Speichern public class ClassCastException extends RuntimeException // unerlaubte Klassenumwandlung public class ClassNotFoundException extends Exception // Zu ladende Klasse ist nicht vorhanden public class CloneNotSupportedException extends Exception // Clonen ist nicht erlaubt public class IllegalAccessException extends Exception // unerlaubter Zugriff public class IllegalArgumentException extends RuntimeException // falsches Argument public class IllegalMonitorStateException extends RuntimeException // siehe Kapitel über Threads public class IllegalThreadStateException extends IllegalArgumentException // unerlaubter Methodenzugriff in diesem Zustand des Threads public class IndexOutOfBoundsException extends RuntimeException // unerlaubter Zugriff public class InstantiationException extends Exception // Ausnahme bei der Objekterzeugung public class InterruptedException extends Exception // Unterbrochen public class NegativeArraySizeException extends RuntimeException // negative Feldgröße public class NoSuchFieldException extends Exception // falscher Komponentenzugriff public class NoSuchMethodException extends Exception // falscher Methodenzugriff public class NullPointerException extends RuntimeException // meist: Variable referenziert noch kein Objekt public class NumberFormatException extends IllegalArgumentException // falsches Zahlenformat public class RuntimeException extends Exception // allgemeine Ausnahme zur Laufzeit, muß nicht mit try-catch // abgefangen werden public class SecurityException extends RuntimeException // Ausnahme im Sicherheitsmanagement public class StringIndexOutOfBoundsException extends IndexOutOfBoundsException // unerlaubter String-Zugriff 1.5.1.5 Fortfahren nach Fehlern Wie lautet die Ausgabe des Programmes ? public static void main(String[] args) { int i, base = 0; for (base = 10; base >= 2; --base) { try { i = Integer.parseInt("40",base); System.out.println("40 base "+base+" = "+i); } catch (NumberFormatException e) { System.out.println( "40 ist keine Zahl zur Basis "+base ); } } } Die Reaktion auf eine Ausnahme muss keinesfalls zwangsläufig darin bestehen, das Programm zu beenden. Statt dessen kann auch versucht werden, den Fehler zu beheben oder zu umgehen, um dann mit dem Programm fortzufahren. 85 86 Wird im obigen Programm die try-catch-Anweisung in die Schleife gesetzt, so fährt das Programm nach jedem Fehler fort und versucht, die Konvertierung zur nächsten Basis vorzunehmen: Die Ausgabe des Programms lautet nun: 40 base 10 = 40 40 base 9 = 36 40 base 8 = 32 40 base 7 = 28 40 base 6 = 24 40 base 5 = 20 40 ist keine Zahl zur Basis 4 40 ist keine Zahl zur Basis 3 40 ist keine Zahl zur Basis 2 1.5.1.6 Beispiel MultipleCatchException /* MultipleCatchException.java */ public class MultipleCatchException { public static void main(String[] args) { int i, j, base = 0; String[] numbers = new String[3]; numbers[0] = "10"; numbers[1] = "20"; numbers[2] = "30"; try { for (base = 10; base >= 2; --base) { for (j = 0; j <= 3; ++j) { i = Integer.parseInt(numbers[j],base); System.out.println( numbers[j]+" base "+base+" = "+i ); } } } catch (IndexOutOfBoundsException e1) { System.out.println( "IndexOutOfBoundsException: " + e1.toString() ); } catch (NumberFormatException e2) { System.out.println( "NumberFormatException: " + e2.toString() ); }finally { System.out.println("Hier wird endgueltig aufgeraeumt !"); }; } } 1.5.1.7 Mehrere catch-Klauseln Ein Programm kann auf verschiedene Fehler reagieren, indem es mehr als eine catchKlausel verwendet. Jede catch-Klausel fängt die Fehler ab, die zum Typ des angegebenen Fehlerobjekts zuweisungskompatibel sind. Dazu gehören alle Fehler der angegebenen Klasse und all ihrer Unterklassen. Die einzelnen catch-Klauseln werden in der Reihenfolge ihres Auftretens abgearbeitet. 1.5.1.8 Die finally-Klausel Die try-catch-Anweisung enthält einen optionalen Bestandteil. Mit Hilfe der finally-Klausel, die als letzter Bestandteil einer try-catch-Anweisung verwendet werden darf, kann ein 86 87 Programmfragment definiert werden, das immer dann ausgeführt wird, wenn die zugehörige try-Klausel betreten wurde. Dabei spielt es keine Rolle, welches Ereignis dafür verantwortlich war, dass die try-Klausel verlassen wurde. Die finally-Klausel wird insbesondere dann ausgeführt, wenn der try-Block durch eine der folgenden Anweisungen verlassen wurde: wenn das normale Ende des try-Blocks erreicht wurde wenn eine Ausnahme aufgetreten ist, die durch eine catch-Klausel behandelt wurde wenn eine Ausnahme aufgetreten ist, die nicht durch eine catch-Klausel behandelt wurde wenn der try-Block durch eine der Sprunganweisungen break, continue oder return verlassen werden soll Die finally-Klausel ist also der ideale Ort, um Aufräumarbeiten durchzuführen. Hier können beispielsweise Dateien geschlossen oder Ressourcen freigegeben werden. 1.5.2 Weitergabe von Exceptions 1.5.2.1 Beispiel Divide public class Divide { public static void main( String[] args ) { try { int x = divide( 5, 0 ); } catch ( DivisionByZeroException e ) { System.err.println( "Ausnahme e.getMessage() ); } } bei der Division: " static int divide ( int x, int y ) throws DivisionByZeroException { if ( y == 0 ) throw( new DivisionByZeroException() ); return x / y; } } class DivisionByZeroException extends ArithmeticException { public DivisionByZeroException() { super( "Division durch 0" ); } } Die throw-Anweisung hat dabei den Charakter einer Sprunganweisung. Sie unterbricht das Programm an der aktuellen Stelle und verzweigt unmittelbar zu der umgebenden catchKlausel. Gibt es eine solche nicht, wird der Fehler an den Aufrufer weitergegeben. Tritt der Fehler innerhalb einer try-catch-Anweisung mit einer finally-Klausel auf, wird diese noch vor der Weitergabe des Fehlers ausgeführt. 87 + 88 1.5.2.2 Die catch-or-throw-Regel Bei der Behandlung von Ausnahmen in Java gibt es die Grundregel catch or throw. Sie besagt, dass jede Ausnahme entweder behandelt oder weitergegeben werden muss. Soll eine Ausnahme nicht behandelt, sondern weitergegeben werden, so kann dies einfach dadurch geschehen, dass eine geeignete try-catch-Anweisung nicht verwendet wird. In der Regel gibt es dann jedoch zunächst einen Fehler beim Übersetzen des Programms, denn der Compiler erwartet, dass jede mögliche Ausnahme, die nicht behandelt, sondern weitergegeben wird, mit Hilfe der throws-Klausel zu deklarieren ist. Dazu wird an das Ende des Methodenkopfes das Schlüsselwort throws mit einer Liste aller Ausnahmen, die nicht behandelt werden sollen, angehängt: Tritt eine Ausnahme ein, sucht das Laufzeitsystem zunächst nach einer die Anweisung unmittelbar umgebenden try-catch-Anweisung, die diesen Fehler behandelt. Findet es eine solche nicht, wiederholt es die Suche sukzessive in allen umgebenden Blöcken. Ist auch das erfolglos, wird der Fehler an den Aufrufer der Methode weitergegeben. Dort beginnt die Suche nach einem Fehler-Handler von neuem, und der Fehler wird an die umgebenden Blöcke und schließlich an den Aufrufer weitergereicht. Enthält auch die Hauptmethode keinen Code, um den Fehler zu behandeln, bricht das Programm mit einer Fehlermeldung ab. Da alle Fehler, die nicht innerhalb einer Methode behandelt werden, dem Compiler mit Hilfe der throws-Klausel bekanntgemacht werden, kennt dieser zu jeder Methode die potentiellen Fehler, die von ihr verursacht werden können. Mit diesen Informationen kann der Compiler bei jedem Methodenaufruf sicherstellen, dass der Aufrufer seinerseits die catch-or-throwRegel einhält. 1.5.2.3 Auslösen von Ausnahmen Mit Hilfe der throw-Anweisung kann ein Objekt aus einer Fehlerklasse dazu verwendet werden, eine Ausnahme zu erzeugen. Die throw-Anweisung kann nicht nur dazu verwendet werden, neue Fehler auszulösen. Sie kann ebenfalls eingesetzt werden, um innerhalb der catch-Klausel einer try-catchAnweisung das übergebene Fehlerobjekt erneut zu senden. In diesem Fall wird nicht noch einmal dieselbe catch-Klausel ausgeführt, sondern der Fehler wird gemäß den oben genannten Regeln an den umgebenden Block bzw. den Aufrufer weitergegeben. Auch selbstdefinierte Ausnahmen müssen sich an die catch-or-throw-Regel halten. Wird die Ausnahme nicht innerhalb derselben Methode behandelt, ist sie mit Hilfe der throws-Klausel im Methodenkopf zu deklarieren und weiter oben in der Aufrufkette zu behandeln. 1.6 Globalisierung 1.6.1 Problem Manche Klassen sind dafür gedacht, in Programmen eingesetzt zu werden, die nicht nur im eigenen Land Anwender finden sollen. Programmierer solcher Klassen sollten darauf achten, dass die Aus- und Eingaben des Programms den jeweiligen nationalen Besonderheiten entsprechen. Die beiden wichtigen Klassen für die Sprachanpassung sind Locale und ResourceBundle. 1.6.2 Beispiel LocalizedClock Die folgende Uhr wechselt in jeder Sekunde ihr Aussehen, indem sie alle verfügbaren besonderen Zeitformate nacheinander anzeigt. import java.util.Locale; 88 89 import java.util.Date; import java.text.DateFormat; public class LocalizedClock extends Thread { private Locale[] locales = null; public static void main( String[] args ) { new LocalizedClock().start(); } public LocalizedClock() { locales = DateFormat.getAvailableLocales(); } public void run() { int localeIndex = 0; while ( isAlive() ) { System.out.print( "\r" + DateFormat.getDateTimeInstance( DateFormat.DEFAULT, DateFormat.DEFAULT, locales[ localeIndex ]).format( new Date() ) + " -- Locale: " + locales[ localeIndex ].toString() + " " ); if ( ++localeIndex >= locales.length - 1 ) localeIndex = 0; try { sleep( 1000 ); } catch ( InterruptedException e ) {} } } } Der Konstruktor bestimmt die vorhandenen Datumsformate und speichert sie in der Instanzvariablen locales ab. Die Methode run() geht das gespeicherte Formatfeld sekundenweise Schritt für Schritt durch, zeigt Datum und Uhrzeit in dem entsprechenden Format und den Namen dieses Formats an. 1.6.3 Die Klasse Locale Locale ist vergleichbar mit dem Context für die Grafikausgabe. A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user. For example, displaying a number is a locale-sensitive operation--the number should be formatted according to the customs/conventions of the user's native country, region, or culture. The first argument to constructor is a valid ISO Language Code. These codes are the lower-case two-letter codes as defined by ISO-639. You can find a full list of these codes at a number of sites, such as: http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt The second argument to constructor is a valid ISO Country Code. These codes are the upper-case two-letter codes as defined by ISO-3166. You can find a full list of these codes at a number of sites, such as: http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html The second constructor requires a third argument--the Variant. The Variant codes are vendor and browser-specific. For example, use WIN for Windows, MAC for Macintosh, and POSIX for POSIX. If you want to see whether particular resources are available for the Locale you construct, you must query those resources. For example, ask the NumberFormat for the locales it supports using its getAvailableLocales method. The JDK provides a number of classes that perform locale-sensitive operations. For example, the NumberFormat class formats numbers, currency, or percentages in a localesensitive manner. Classes such as NumberFormat have a number of convenience methods for creating a default object of that type. 89 90 A Locale is the mechanism for identifying the kind of object (NumberFormat) that you would like to get. The locale is just a mechanism for identifying objects, not a container for the objects themselves. 1.6.4 Die Klasse DataFormat DateFormat is an abstract class for date/time formatting subclasses which formats and parses dates or time in a language-independent manner. The date/time formatting subclass, such as SimpleDateFormat, allows for formatting (i.e., date -> text), parsing (text -> date), and normalization. The date is represented as a Date object or as the milliseconds since January 1, 1970, 00:00:00 GMT. getAvailableLocales() Gets the set of locales for which DateFormats are installed. getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale) Gets the date/time formatter with the given formatting styles for the given locale. Statische Funktion, liefert DateFormat Objekt als ein Formatierobjekt format(Date date) Formats a Date into a date/time string. 1.6.5 Beispiel SimpleGlobalMenue public class SimpleMenue extends JFrame { public SimpleMenue() { super("Swing-Globalisierungstest"); addWindowListener(new WindowClosingAdapter()); // Locale locale = Locale.US; Locale locale = Locale.GERMANY; ResourceBundle labelBundle = ResourceBundle.getBundle( "ViewerBundle", locale ); String [] labels labels[ 0 ] = labels[ 1 ] = labels[ 2 ] = = new String[7]; labelBundle.getString( "searchButton" ); labelBundle.getString( "clearButton" ); labelBundle.getString( "executeButton" ); JPanel bp = new JPanel(); getContentPane().setLayout(new BorderLayout()); getContentPane().add("North", bp); bp.add(new JButton(labels[0])); bp.add(new JButton(labels[1])); bp.add(new JButton(labels[2])); } public static void main(String[] args) { SimpleMenue frame = new SimpleMenue(); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } public class ViewerBundle extends ListResourceBundle { static final Object[][] labels = { { "searchButton", "search" }, { "clearButton", "clear" }, { "executeButton", "execute" }}; public Object[][] getContents() { return labels; } } public class ViewerBundle_de extends ViewerBundle { static final Object[][] contents = { 90 91 { "searchButton", "Suchen" }, { "clearButton", "Loeschen" }, { "executeButton", "Starten" }}; public Object[][] getContents() { return contents; } } public class ViewerBundle_en extends ViewerBundle { static final Object[][] labels = { { "searchButton", "search" }, { "clearButton", "clear" }, { "executeButton", "execute" }}; public Object[][] getContents() { return labels; } } 1.6.6 Die Klasse ResourceBundle ResourceBundle objects represent a bundle of resources, which belong to a Locale object. There is no object, which represents the set of bundles. The set of bundles is modeled by static functions of the ResourceBundle class. When the program needs a locale-specific resource, a String for example, the program can load it from the resource bundle that is appropriate for the current user's locale using the static getBundle method. Resource bundle objects contain key/value pairs. The keys uniquely identify a locale-specific object in the bundle. Each related subclass of ResourceBundle has the same base name plus an additional component that identifies its locale. Each related subclass of ResourceBundle contains the same items, but the items have been translated for the locale represented by that ResourceBundle subclass. 2 Die Unified Modeling Language (UML) und der Rational Unified Software Development Process (RUP) 2.1 UML im Überblick 2.1.1 2.1.1.1 Dokumentation Wozu dient die Dokumentation Die Dokumentation dient: Zur Reflexion der eigenen (manchmal wirren) Gedanken, Zur Abspeicherung der eigenen Überlegungen im Hinblick auf einen Personalwechel oder eine spätere Wartung, Zur Unterstützung der Kommunikation mit Kollegen während der Analyse und Entwurfsphasen Zur Unterstützung vertraglicher Vereinbarungen 2.1.1.2 Anmerkungen zur Dokumentation In allen Ingenieurdisziplinen werden üblicherweise Zeichnungen angefertigt. Man denke z.B. an den Haus- oder Maschinenbau. Diese Zeichnungen werden an Subunternehmer ausgeliefert, dienen als Grundlage für die Kosten- und Zeitabschätzung, für die Ressourcen und Arbeitsverteilung. Die Zeichnungen sind das Modell des entstehenden Produkts. Eine wohldefinierte und ausdrucksstarke Notation ermöglicht erst die Kommunikation mit anderen Entwicklern oder Kunden. 91 92 Die Designer müssen sicherstellen, dass das Modell die unterschiedlichen Anforderungen an das Produkt berücksichtigt. Das Durchdenken alternativer Modelle führt zu einem tieferen Verständnis des Systems und damit letztendlich zu einem besseren Produkt. Ein Problem der heutigen Softwareentwicklung besteht darin, dass zu schnell programmiert wird. Damit befindet man sich bereits auf einem niedrigen Abstraktionslevel der Systementwicklung. Die Gründe hierfür sind vielfältig. Manager werden ängstlich, wenn kein Code produziert wird. Programmierer fühlen sich beim Programmieren sicherer als beim Bilden abstrakter Modelle. Insgesamt wird aber inzwischen die Bildung von Modellen in der Software Engineering Community genauso akkzeptiert wie in anderen Ingenieurdisziplinen auch. Die Dokumentation ist also wichtig ! Sie sollte aber nicht den Entwicklungsprozess steuern ! Hier hilft eine angemessene Formalisierung der Vorgehensweise. Bei einem 1-MannWegwerf-Programm für 1 Tag genügt ggf. eine Kritzelei auf einem Briefumschlag. Bei 100Mann-Projekten sind dagegen Bücher mit Entwurfsbeschreibungen erforderlich. Wenn die Designer u. Programmierer eines Systems hochqualifiziert sind und bereits seit einiger Zeit eng zusammenarbeiten, kann die Dokumentation etwas sparsamer ausfallen als bei einer neu zusammengestellten Mannschaft von Hochschulabgängern. Ein zu hoher Dokumentationsaufwand stabilisiert möglicherweise den Zustand eines Systems und verhindert damit die für eine Weiterentwicklung und Verbesserung erforderlichen Iterationen. Daher sollten zur Minimierung des Aufwandes möglichst CASETools verwendet werden. Die Dokumentation sollte inkrementell mit dem System wachsen. Sie bildet dann die Basis für die Reviews zur Qualitätssicherung. 2.1.2 Geschichte der UML Bezeichnungen von einigen der bisher verwendeten Methoden : Booch Methode OMT von James Rumbaugh OOSE/Objectory von Ivar Jacobson Coad Yourdon oder OOA/OOD Jede Methode hatte ihre eigenen Notationen, ein eigenes Vorgehensmodell oder Prozess und ihre eigenen Tools. Im Laufe der Zeit ähnelten sich die Methoden immer mehr. Dies wurde von den MethodenGurus erkannt und führte zur Entwicklung von UML. Ein zentrales Ziel bei der Entwicklung von UML war die Beendigung des Methodenkriegs. Die Unified Modelling Language (UML) ist eine visuelle Modellierungssprache und wurde von Grady Booch, James Rumbaugh und Ivar Jacobson entworfen. UML ist offen, d.h. Firmen dürfen die Sprache frei benutzen, Toolhersteller dürfen passende CASE-Tools erstellen. 1996 entstand ein UML Partner Konsortium aus Rose, DigitalEquipment, HP, IBM, Microsoft, Oracle, Texas Instruments und weitere Organisationen. Im Januar 1997 wurde die Version 1.0 ausgeliefert. Die führende Organisation zur Standardisierung im objektorientierten Bereich, OMG (Object Managment Group), hat die UML im Nov 97 als Standard akzeptiert und damit die Verantwortung für die weitere Entwicklung der UML übernommen. 2.1.3 Views 92 93 Eine in irgend einem Sinne vollstandige Darstellung eines Systems ist bei komplexen Systemen nicht möglich. Z.B. lässt sich ein Automotor niemals in einer einzigen Zeichnung darstellen. Die Darstellungen zeigen in der Regel nur ausgewählte Ansichten (Views) des Systems. Z.B. zeigen Darstellungen eines Softwaresystems das Vererbungsnetz bestimmter Schlüsselklassen oder alle Klassen, die von einer bestimmten Klasse verwendet werden oder die Verteilung auf die Hardware oder die Verzeichnisstruktur. Die Einteilung und die Bezeichnung der Views hängt von der Sicht des Autors ab. Vgl. RefMan S.24 2.2 RUP im Überblick Jede Softwareerstellung sollte in einem festgelegten organisatorischen Rahmen erfolgen. Ein Software Development Process definiert wer, was, wann und wie tun soll, um ein bestimmtes Ziel zu erreichen. Der Prozess dient damit als Hilfe für die beteiligten Entwickler. Der Rational Unified Process beschreibt die durchzuführenden Aktivitäten (Activities) geordnet nach sogenannten Disciplines und die Reihenfolge des Arbeitsablaufes geordnet nach sogenannten Phasen. Der Rational Unified Process basiert auf dem 1987 veröffentlichten Objectory Process (Ericcson) und der 1997 standardisierten UML. 2.3 Aktivitäten des RUP und die zugehörigen UML Diagramme Man strukturiert die für ein Softwareprodukt aufzuwendenden Aktivitäten gemäß folgender Disciplines Business Modeling Requirements Analyse Design Implementation Test Deployment Configuration&Change Management Project Management Environment Business Modeling wird in einer eigenen Vorlesung behandelt. 2.3.1 Business Modeling Der Zweck des Business Modeling besteht darin, die Struktur und die Dynamik einer Firma zu verstehen, die aktuellen Probleme kennen zu lernen und Möglichkeiten für Verbesserungen zu erkunden, Anforderungen für Softwaresysteme zur Automatisierung abzuleiten. Das Business Modeling ist Gegenstand einer eigenen Vorlesung. Wir betrachten im Folgenden ausschließlich Beispiele, bei denen der „Geschäftsprozess“ einfach und aus dem täglichen Leben bekannt ist. Als Beispiele betrachten wir Getränkeautomat und Bibliotheksystem. 93 94 2.3.2 Requirements 2.3.2.1 Erstellen einer Vision 2.3.2.1.1 Beispiel Getränkeautomat Die AutomatenApplication steuert die Ausgabe eines vom Kunden selektierten Gerätes. Die Bezahlung kann in bar (einschließlich Scheinannahme) und per Karte erfolgen. Die Bedienung erfolgt über Touchscreen. Der Automat informiert einen zentralen Monitor über besondere Ereignisse. Der Status des Automaten kann vom zentralen Monitor erfragt werden. Im Rahmen der Wartung soll eine statistische Auswertung möglic sein. Das System soll einfach wartbar sein. 2.3.2.1.2 Beispiel Bibliothek Bibliothek: Vision Die Bibliothekssoftware soll die Buchausleihe an der FH unterstützen Bücher und Leser sollen registriert werden können. Bücher sollen schnell gesucht werden können. Bücher sollen reserviert werden können. Automatische Mahnungen sollen versendet werden können. Zur Einsparung von Personal soll ein automatisches Transportsystem mit Hochlager und Scanner zur Verfügung stehen. Eine Fernausleihe soll möglich sein. Use Case Buch Ausleihen Gutfall: Das System zeigt das Login Formular Der Kunde editiert Name und Passwort Das System zeigt die BuchNavigationsAnsicht Der Kunde selektiert ein Buch und drückt „Ausleihen“ Das System beauftragt das HochregalSystem mit der Beschaffung und den Ausgabeautomaten mit der Ausgabe Der Kunde logged sich aus. Schlechtfall: 2.3.2.1.3 Konzept Die Vision definiert die Kundensicht des Products. Sie enthält eine knappe Darstellung der wesentlichsten Bedürfnisse, ausgedrückt in der Terminologie des Kunden. Die Vision bildet die Basis für die detaillierte Auflistung der Requirements. 2.3.2.2 Aufnehmen der Stakeholder Requests Stakeholder = customer, end user, marketing person, and so on Liste mit allen vom Kunden formulierten Anforderungen für das System. Technisch werden diese Requests als Change Requests verwaltet. 94 95 2.3.2.3 Erstellen eines Use Case Models 2.3.2.3.1 Beispiel Getränkeautomat Gutfall Touchscreen zeigt die vorhandenen Flaschentypen Der Kunde drückt den Button mit dem Cola Icon (Der Automat prüft, ob Cola vorhanden ist) Der Automat zeigt den Preis. Der Kunde wirft zu viel Geld ein. Der Automat wirft eine Cola Flasche aus. Der Automat gibt den überzahlten Betrag zurück. Schlechtfall …wie oben Der Kunde wirft einen Hosenknopf ein Der Automat gibt den Knopf zurück Der Automat zeigt die Meldung „Unzulässiges Zahlungsmittel“ Schlechtfall: …wie oben Der Automat zeigt die Meldung „Cola leer“ Der Automat gibt den eingegebenen Betrag zurück Grafik Flaschenfüller Kunde Lieferant Kauf Geldentnehmer männlich weiblich Rückgabe Systemstatus Wartungstechniker Softwareupdate Flaschenausgeber Geldausgeber 2.3.2.3.2 zentraler Monitor Beispiel Bibliothek 95 96 Ausleihe Fernausleihe Leser Hochregallager Rückgabe Datenbank Bibliothekar Buchpflege E-Mail Server Wartungstechniker Leserpflege Scanner Ausgabeautomat 2.3.2.3.3 Überblick Ein Use Case Diagram zeigt eine Reihe externer Actors und deren Beziehungen zu den Use Cases. Der Actor kann ein Mensch oder ein anderes Softwaresystem oder ein Gerät sein. Use Cases unterstützen die Entscheidungen über funktionale Anforderungen beschreiben die funktionalen Anforderungen unterstützen die Überprüfung der Vereinbarkeit unterschiedlicher Anforderungen erleichtern die Kommunikation zwischen den Entwicklern bilden die Basis für den Entwurf des Systems bilden die Basis für spätere Tests erleichtern die Verfolgung der Anforderungen zu den zugehörigen Klassen und Funktionen 2.3.2.3.4 System Das System wird durch ein Rechteck beschrieben. Das System muss nicht notwendigerweise ein Softwaresystem sein. Es kann auch eine Maschine oder eine Firma sein. 2.3.2.3.5 Actor 2.3.2.3.5.1 Charakterisierung Ein Actor interagiert mit dem System indem er Nachrichten sendet und empfängt. 96 97 Der Actor ist ein Typ und keine Instanz. Er repräsentiert damit eine Rolle und nicht einen individuellen Nutzer des Systems. Der Actor hat einen Namen, der seine Rolle reflektiert. Ein Use Case wird immer durch einen Actor initiiert, der eine Nachricht an das System sendet (Stimulus). Die Aktoren bilden den Context des Systems. Der Context eines Subsystems oder einer Klasse besteht in der Regel ausschließlich aus Software. Ein Actor Model Element hat als Standard Icon ein Strichmännchen. 2.3.2.3.5.2 Beziehungen zwischen Actors In Use Cases werden z.B. Vererbungsbeziehungen benutzt, um das gemeinsame Verhalten von Actors zu beschreiben. 2.3.2.3.6 Use Case 2.3.2.3.6.1 Charakterisierung Ein Use Case ist definiert als eine Menge von Sequenzen von Actions, welche ein beobachtbares Resultat für einen bestimmten Actor ergeben. Jeder Use Case beschreibt eine spezifische Funktionalität des Systems. Ein Use Case wird immer von einem Actor initiiert. Ein Use Case liefert immer einen Wert an den Anwender. Dieser Wert repräsentiert den Wunsch des Anwenders. Im Verlauf eines Use Case können Interaktionen mit dem Actor stattfinden (z.B. Maskeneingaben). Ein Use Case ist erst dann vollständig , wenn der Endwert produziert wurde. Die Verbindung Actor Use Case wird auch als Communication Association bezeichnet. Ein Use Case ist eine Klasse und keine Instanz. Er beschreibt die Funktionalität als Ganzes einschließlich Alternativen, Fehler und Exceptions. Eine Instanz des Use Case ist ein Scenario. Ein Scenario stellt einen ganz bestimmten Ausführungspfad dar, z.B. einen Fehlerpfad. Use Cases können für das gesamte System wie für einzelne Klassen erstellt werden. 2.3.2.3.6.2 Spezifikation Die Beschreibung des Use Case erfolgt meist in Textform. Sie sollte folgende Aussagen enthalten : Was soll mit dem Use Case erreicht werden ? Welcher Actor intiiert den Use Case in welcher Situation ? Welche Botschaften tauschen Actor und System aus ? Welche alternativen Ausführungen können in Abhängigkeit von welchen Bedingungen auftreten ? Wann ist der Use Case beendet und welchen Wert liefert er an den Actor ? 97 98 Eine allgemeine textuelle Beschreibung kann durch spezielle Szenarien ergänzt werden. Diese beschreiben eine spezifische Situation unter Verwendung konkreter Instanzen von Actor und Use Case. Meist beschreibt man ein Szenario für den Normalfall (Main Flow of Events) und mehrere Szenarien für besondere Ausnahmesituationen (Exceptional Flow of Events). Jede Sequenz stellt einen der möglichen Kontrolflüsse dar. Szenarios bilden die Instanzen eines Use Case. Die graphische Darstellung der Szenarien erfolgt typischerweise mit einem Sequenzdiagram (vgl. weiter unten). Ein Use Case kann auch durch ein Activity Diagram oder ein Statechart Diagram (vgl. weiter unten) beschrieben werden. Use Cases werden in der Sprache und Teminologie des Kunden beschrieben. Die Funktionalität wird so beschrieben wie sie der Nutzer wahrnimmt. Insbesondere wird nicht beschrieben, wie das System die Funktionalität erfüllt. Eine nützliche Methode zum Beschreiben und Durchspielen der Use Cases besteht im Rollenspiel. Eine Person spielt die Rolle des Actors, eine andere die Rolle des Systems. 2.3.2.3.6.3 Beziehungen zwischen Use Cases Generalization Relationship Das Kind kann überall dort verwendet werden, wo der Vater erscheint. Z.B. kann der Use Case „Identifizierung eines Kunden“ durch den Use Case „Scannen der Netzhaut“ oder den Use Case „Pruefen des Passwortes“ spezialisiert werden. Include Relationship Wenn mehrere Use Cases dieselben Szenarien beinhalten, können diese in einem gemeinsamen Use Case modelliert werden. Z.B. beinhalten die Use Cases „GeldWechseln“ und „KontostandAnzeigen“ das Verhalten des Use Case „ValidiereKunden“. Grouping Use Cases können mittels Packages (vgl. weiter unten) gruppiert werden. ???Extends Relationship Ein Use Case kann einen anderen erweitern. Dies bedeutet, dass er einige Szenarien beinhaltet, die ggf. optional an bestimmten Stellen des erweiterten Use Cases in ähnlicher Weise wie Unterprogramme durchlaufen werden. 2.3.2.3.7 Erweiterung der UML Die Klassifizierung der Model Elemente erfolgt durch sogenannte Stereotypen. Das Strichmännchen ist das Standard Icon für den Stereotyp <<Actor>>. Die Stereotypen können in der Regel durch ihr spezifisches Icon oder als Rechteck mit Label <<Stereotyp>> dargestellt werden. Das Vokabular der UML kann erweitert werden. Ein neuer selbst definierter Stereotype definiert eine neue Art von Model Element. Die UML ermöglicht damit, fehlende Model Elemente zu definieren. Z.B. könnte man im Rahmen eines Netzwerkdesigns Symbole für Router erfinden. Einem Stereotype kann ein eigenes Icon zugewiesen werden, z.B. Skizze eines Routers, eine spezielle Semantik und spezielle Eigenschaften. Gemeint sind hier die Eigenschaften des Model Elementes, wie Name , Attribute und Operationen im Falle einer Klasse. 2.3.2.3.8 Wie findet man Actors und Use Cases 98 99 Man identifiziert zunächst einen Actor und überdenkt dann dessen Interaktion mit dem System. Wie findet man Actors ? Man beantworte etwa folgende Fragen : Wer wird die wesentliche Funktionalität des Systems nutzen ? Wer wird das System warten und administrieren ? Welche Hardwaregeräte benötigt das System ? Mit welchen anderen Systemen wird das zu entwickelnde System interagieren ? Wer hat ein Interesse an den Werten, die das System produziert ? Wie findet man Use Cases ? Für jeden Actor beantworte man etwa folgende Fragen : Welche Funktionen erwartet der Actor von dem System ? Was muss er selbst dazu beitragen ? Muss der Actor irgend eine Art von Information lesen, erzeugen, zerstören, modifizieren oder speichern ? Muss der Actor über irgendwelche Ereignisse im System unterrichtet werden oder muss er das System über etwas informieren ? Welche Funktionalität ist hierzu nötig ? Kann die tägliche Arbeit des Actors vereinfacht oder effizienter gestaltet werden ? Welchen Input/Output benötigt das System und von wo kommt dieser ? Was sind die wesentlichen Probleme mit der aktuellen Implementation des Systems ? 2.3.2.4 Erstellen von Prototypen für User Interfaces Die Anwender sollten verschiedene User Interfaces ausprobieren können. 2.3.2.4.1 Beispiel Bibliothek 99 100 2.3.2.4.2 Getränkeautomat // Einfügen 2.3.2.5 Festlegen der Supplementary Requirements 2.3.2.5.1 Beispiel Bibliothek // Einfügen 2.3.2.5.2 Beispiel Getränkeautomat Supplementary Requirements 100 101 Die Ausgabe der Flasche nach Einwurf des Geldes soll in 90% der Fälle nach 2 Sek erfolgen. Die Downtime (Automat aus technischen Gründen nicht funktionsfähig) soll unter 1 Tag pro Monat liegen. 80% der Personen sollen den Automaten innerhalb von 10 Sekunden bedienen können. Vorraussetzung für die Auslieferung ist die Bereitstellung des Flaschengebers 2 Monate vor dem Auslieferungstermin. 2.3.2.6 Festlegen der Supplementary Requirements 2.3.2.6.1 Beispiel Bibliothek // Einfügen 2.3.2.6.2 Beispiel Getränkeautomat // Einfügen 2.3.2.6.3 2.3.2.6.3.1 Fragenkatalog für Supplementary Requirements (aus Industrieprojekt für Bildverarbeitung) Auslieferungstermine/Zeitresourcen Vom Kunden geforderter Zeitplan /Deadline 2.3.2.6.3.2 Kostengrenze/Budget/Geldresourcen … 2.3.2.6.3.3 Nationale oder Internationale Regularien Sicherheitsstandards USA /China / Germany 2.3.2.6.3.4 Conditions Beispiel Die BV Bibliothek steht am ... zur Verfügung Hardware muß zur Verfügung stehen Lieferanten müssen Schnittstellen Definitionen liefern 2.3.2.6.3.5 Produktsupport Schulungen / Einführung beim Kunden – Wie lange? Online Support / Hotline 2.3.2.6.3.6 Juristische Anforderungen Übereinstimmung mit technischen Standards ISO9000, CMM-Level 3, ANSI standard 2.3.2.6.3.7 Technologische Randbedingungen für die Lösung (Solution Constrains) 101 102 z.B. Verwendung von NT, MFC für GUI, CanBus usw. Look and Feel des User Interfaces Sprache zwischen Deutsch und Englisch umschaltbar (multilingual) 2.3.2.6.3.8 Anforderungen bzgl. Datenvolumen Objekt-Persistenz mittels Datenbanken oder Dateien, Datenvolumen 2.3.2.6.3.9 Zeitanforderungen/Performance Antwortzeit Durchsatzraten 2.3.2.6.3.10 Anforderungen an die Präzision des Systems Fehlertoleranz / Präzision Beispiel: Die Fehlertoleranz für die Tischpositionierung soll unter 10% des Fahrbereichs liegen. 2.3.2.6.3.11 Lastanforderungen Belastung des Systems / Zuverlässigkeit Z.B. Mehrbenutzerbetrieb mit der maximalen Benutzeranzahl 100. 2.3.2.6.3.12 Anforderungen an Fehlertoleranz, Robustheit des Systems Verfügbarkeit des Systems 2.3.2.6.3.13 Anforderungen an die Benutzbarkeit Ease of Use /GUI Ease of Learning (Schulungsaufwand) 2.3.2.6.3.14 Anforderungen an die technische Sicherheit bzgl. Anwender und collaborierende Hardware Z.B. Der Roboter darf nie die Grenzen des Moduls überschreiten. 2.3.2.6.3.15 Anforderungen an die Datensicherheit Zugriffsschutz / Passwörter (verschlüsselt) 2.3.2.6.3.16 Anforderungen an die Interoperabilität mit anderen Systemen/ Installationsumgebung (Implementation Environment) Zusammenspiel mit Netzwerk, Hardware. Referenzen zu den Schnittstellen der collaborierenden Systeme 102 103 2.3.2.6.3.17 Anforderungen an die Dokumentation (Benutzer+Wartung) Technische Dokumentation Benutzeranleitung 2.3.2.6.3.18 Anforderungen an die Wartbarkeit … 2.3.2.6.3.19 Anforderungen an die Portabilität z.B. Software soll leicht von C++ auf Java portierbar sein, 2.3.2.7 Erstellen einer Liste für mögliche Anforderungen 2.3.2.7.1 Beispiel Bibliothek Alle Ideen werden aufgelistet und ggf. mit Attributen versehen. 2.3.2.7.2 Beispiel Getränkeautomat List of Requirements Requirement Identifier Flasche kaufen Flasche zurückgeben Ermittle Status Geld entnehmen Flasche auffüllen System testen Priority StatusBenefitEffort Risk Stability Remarks 3 2 1 3 2 1 3 2 1 3 3 2 2 3 1 1 1 2 2 3 Alle Ideen werden aufgelistet und ggf. mit Attributen versehen. 2.3.2.8 Erstellen eines Glossary Das Glossary enthält wichtige Begriffsdefinitionen. 2.3.2.8.1 Beispiel Bibliothek Title = identifiziert ein Buch durch dessen ISBN Nummer item = identifiziert ein Exemplar eines Buches durch die Bibliotheks ID 2.3.2.8.2 Beispiel Getränkeautomat Getränkemarke = Identifiziert alle Getränke eines Herstellers mit denselben Inhaltsstoffen Lieferantennummer = 6stellige Nummer zur Identifikation des Lieferanten 103 104 2.3.2.9 Beispiel Bankautomat ValidateUser ACustomer CheckPassword AIndividualCustomer RetinalScan ACorporateCustomer ChangeMoney ShowBalance Textuelle Beschreibung eines Use Case Bankautomat ValidateUser Main flow of events : Das System bitte den Kunden um seine PIN. Der Kunde gibt seine PIN über die Tastatur ein. Der Kunde bestätigt die Eingabe mittels Enter Button. Das System überprüft die PIN. Falls die PIN gültig ist, bestätigt das System die Gültigkeit. Exceptional flow of events : Der Kunde cancelt die Transaktion über den Cancel Button. Das System ändert nicht den Account des Kunden. Exceptional flow of events : Der Kunde korrigiert seine PIN vor der Betätigung der Enter Taste. Exceptional flow of events : Der Kunde gibt eine ungültige PIN ein. Daraufhin startet die Eingabe erneut. Nach dreimaliger Fehleingabe in Folge wird die gesamte Transaktion abgebrochen. 2.3.3 Analyse & Design 2.3.3.1 Identifizierung von Klassen und Objekten 2.3.3.1.1 Beispiel Bibliothek // Einfügen 2.3.3.1.2 Beispiel Getränkeautomat 104 105 Entity Objects: Geld Getränke Lieferanten Boundary Objects: Zentraler Monitor Geld-Ein-/Ausgeber Temperatursensor Control Objects Geldprüfer AusgabeManager 2.3.3.1.3 Grundlegende Probleme Einige Zitate : Booch : „... Der Prozess objektorientierter Analyse und Designs kann nicht in einem Kochbuch beschrieben werden ...“ Booch : „Wenn wir klassifizieren, versuchen wir Dinge zu gruppieren, die eine gemeinsame Struktur oder ein gemeinsames Verhalten aufweisen. Es gibt keine einfachen Rezepte, keine "perfekte" Klassenstruktur und nicht die "richtige" Objektmenge. Designentscheidungen stellen immer einen Kompromiss dar, der von vielen gegeneinander abzuwägenden Faktoren abhängig ist. Der Designer muss z.B. die Wahrscheinlichkeit für Änderungen einschätzen. Solche Abschätzungen basieren auf Erfahrung und erfordern Wissen über den Einsatzbereich der Applikation sowie ein Verständnis für Hardware- u. Softwaretechnologie. Die besten Software-Designs sehen immer einfach aus. Intelligente Klassifizierung entsteht am besten in einem inkrementellen und iterativen Prozess.“ Dijkstra "Die Herausforderung der Programmierung ist eine umfassende Aufgabe angewandter Abstraktion und erfordert deshalb die Fähigkeiten eines Mathematikers, kombiniert mit der Vorgehensweise eines kompetenten Ingenieurs" Es gibt kein allgemeines Rezept, welches Intelligenz, Erfahrung und guten Geschmack beim Programmieren ersetzt. Grundprinzipien zur Bewältigung von Komplexität : Es gibt nur einen grundlegenden Weg zur Bewältigung von Komplexität : Zerlegung (divide et impera teile und herrsche) und Abstraktion. Da wir meist nicht in der Lage sind, ein komplexes Objekt in seiner Ganzheit zu verstehen, ignorieren wir einfach die unwichtigen Details und beschäftigen uns statt dessen mit dem idealisierten Modell des Objekts. Wir reduzieren damit die Information auf ein für uns erträgliches Maß. Abstraktion bedeutet Informationsreduktion. Die im Folgenden beschriebenen Vorgehensweisen bieten dennoch eine gewisse Hilfestellung. 2.3.3.1.4 Studieren des Problem Domains Welche Klassen, Objekte enthält der Problembereich ? 105 106 Die Betrachtung der folgenden Stereotypen ist hilfreich : 2.3.3.1.4.1 Entity Class Diese Klassen repräsentieren reale Objekte aus dem vom System verwalteten Bereich (Business Objects) Die Entity Klassen repräsentieren meist die logische Strukturierung der Informationen, die das System verwaltet. Beispiele Reale Dinge : Bankkunde, Konto, Bücher, Autos, Flugzeuge, Personen, Technische Geräte Ereignisse : Bestellung, Anfrage, Landung Orte : Koordinaten, Adressen Organisationen : Firma, Verein 2.3.3.1.4.2 Boundary Class Ein Objekt der Boundary Class interagiert mit einem Actor ausserhalb des Systems. Beispiele Auswahlfenster, Kartenleser, Sensor für die Geldentnahme, Temperatursensor Man ordnet jedem externen Gerät eine Boundary Class zu. Man ordnet jedem externen Softwaresystem eine Boundary Class zu. Man ordnet jedem menschlichen Actor ein Primary Window (Boundary Class) zu. Man ordnet jedem Entity Object, mit dem der Actor interagiert, ein untergeordnetes Fenster (Boundary Class) zu. 2.3.3.1.4.3 Control Class Die Objekte der Control Class kontrollieren und koordinieren Interaktionen. Beispiele BestellungsManager, KreditPrüfer, UserInterfaceDirector Ein solches Objekt kann z.B. einen einzigen Use Case überwachen und steuern oder eine Menge untergeordneter Entity Objekte managen. Eine solche Steuerung beinhaltet oft die Überwachung von Geschäftsregeln (Business Logic), die keinem Entity Object zugeordnet werden können. Man kann etwa jedem Use Case eine Control Class zuordnen. Dies ist allerdings unnötig, wenn der Mensch wesentliche Teile der Steuerung durch Interaktion mit dem System selbst übernimmt oder ein einziges Steuerungsobjekt für alle Use Cases ausreicht. 2.3.3.1.5 Studium der sprachlichen Beschreibung des Problems Man studiert die sprachliche Beschreibung der Vision und der Use Cases. Substantive sind Objektkandidaten. Verben sind potentielle Elementfunktionen. Häufig beschreiben Nomen o. Adjektive erforderliche Klassen (z.B. speicherfähig, gleichzeitig). 106 107 Achtung : Verben können substantiviert werden. Sprache ist meist ungenau. 2.3.3.1.6 Studium existierender Software Man studiert z.B. das aktuelle Computer System einer Bibliothek oder andere ähnliche Systeme. Die Verwendung von vorangegangener Arbeit als Inspiration ist eine akzeptable Technik für Innovation. 2.3.3.2 Erstellen eines Class Diagrams 2.3.3.2.1 Beispiel Getränkeautomat // Einfügen 2.3.3.2.2 Beispiel Bibliothek // Einfügen 2.3.3.2.3 Ziel // Einfügen Klassen können in vielfältigen Beziehungen zueinander stehen. Z.B. kann eine Klasse eine andere nutzen oder eine Spezialisierung einer anderen Klasse sein. 2.3.3.2.4 UML Darstellung der Klassen Klassen werden durch Rechtecke dargestellt. Attribute und Operationen können optional ausgeblendet werden. Manche Klassen, wie Prozesse, Threads, Hardware Devices, Applets repräsentieren immer wieder verwendete und wichtige architektonische Abstraktionen. Daher werden sie von UML mit eigenen Stereotypen und Icons unterstützt. Im Falle von abstrakten Klassen oder abstrakten Operationen wird der Name kursiv (italic) geschrieben. Eine Class ist ein Spezialfall eines Classifiers. Classifier unfassen Nodes, Use Cases, Components usw. + steht bei Attributen und Operationen für public und – für private und # für protected. Class Scope Attribute (Statische Variable) werden unterstrichen. Class Scope Operationen (Statische Operationen) werden unterstrichen. Klassen Icons können auch verwendet werden, um primitive Typen wie int, char, bool usw. darzustellen. (UML unterscheidet zwischen Operationen und Methoden. Eine Operation spezifiziert einen Service, der von jedem Objekt angefordert werden kann. Eine Methode implementiert den Service. In einem Vererbungsgraph existieren mehrere Methoden für dieselbe Operation. Die zum aktuellen Objekt gehörende Methode wird zur Laufzeit ausgewählt.) 107 108 2.3.3.2.5 Identifizieren der Beziehungen zwischen Klassen 2.3.3.2.5.1 Association 2.3.3.2.5.1.1 Allgemein Ein Link repräsentiert eine Verbindung zwischen zwei Objekten, über die eine Message gesendet werden kann (Java Referenz oder C++ Pointer). Eine Association zwischen Klassen beschreibt eine Menge von Links zwischen Objekten dieser Klassen. Ein Link ist eine Instanz einer Association. Eine Association gibt eine semantische Abhängigkeit ansonsten nicht verwandter Klassen an. Eine Association ist normalerweise bidirektional. D.h. man kann in beide Richtungen navigieren. Ein Pfeil zeigt an, dass die Association nur in einer Richtung verwendet werden kann. Die Navigierbarkeit wird damit für eine Richtung ausgeschaltet. Eine Association hat einen oder zwei Namen für jede Richtung. Mit Hilfe dieser Namen kann die Association „gelesen“ werden. Ferner kann man die Rolle benennen, welche eine Klasse in der Beziehung zu einer anderen übernimmt. Die Rollennamen sind Teil der Association und nicht Teil der Klassen. Die Multiplicity wird am Ziel einer Association angemerkt und gibt die Anzahl der Links zwischen jeder Instanz der Quellklasse und Instanzen der Zielklasse an. Recursive Association Eine Association einer Klasse zu sich selbst wird als Recursive Association bezeichnet. Association Class Wenn detailliertere Informationen über einen Link gespeichert werden müssen, bietet sich die Verwendung einer Klasse an. Zu jedem Link der Association wird dann ein Objekt der Association Class erzeugt. 2.3.3.2.5.1.2 Aggregation Eine Aggregation ist ein Spezialfall der Assiciation und zeigt eine Ganz- /BestandteilBeziehung Nicht immer sind die beiden Klassen an den Enden der Association gleichwertig oder auf demselben Level. Manchmal möchte man ausdrücken, dass die eine Klasse das „Ganze“ repräsentiert und die andere Klasse einen „Teil“. Schlüsselworte zur Identifizierung und Kennzeichnung einer Aggregation sind "part of" , "has a", „consist of“, „contains“ Z.B. enthält jede Instanz der Klasse Auto eine Instanz der Klasse Motor. Die Teil/Ganzes Hierarchie muss nicht unbedingt ein physikalisches Beinhalten bedeuten. Z.B. hat ein Verein viele Mitglieder ohne diese physikalisch zu beinhalten. Auch die Beziehung zwischen dem Vermögen eines Aktionärs u. seinen Anteilen kann als Aggregation betrachtet werden. Das Vermögen enthält Aktien, Anleihen, usw. Eine Aggregation beschreibt oft verschiedene Abstraktionslevel, wenn man das „Ganze“ ohne seine „Bestandteile“ betrachtet (Reduktion von Information). 108 109 Die Realisierung in C++ erfolgt durch Elementobjekt, Zeiger o. Referenz. In Java erfolgt die Realisierung durch Referenz. Auch das enthaltene Objekt darf einen Zeiger auf das umschließende Objekt enthalten (zyklische Zeigerbeziehung). 2.3.3.2.5.1.3 Composition Aggregation Eine Composition Aggregation drückt im strengen Sinne Eigentum aus. Die Teile leben und sterben mit dem Ganzen. Die Multiplicity auf der Seite des Ganzen muss 0 oder 1 sein. Im Falle der Realisierung durch ein C++ Elementobjekt ist die Erzeugung und Vernichtung automatisch an die umschließende Instanz gekoppelt. Im Falle eines Zeigers oder einer Referenz sollten die Bestandteile mit dem umschließenden Aggregat-Objekt erzeugt und vernichtet werden. Die Verantwortlichkeit für die Existenz eines Bestandteils kann durchaus von einer Klasse an eine andere übergeben werden (Shared part class). ???Shared Aggregation Eine Shared Aggrgation ist ein Spezialfall einer Aggregation. Personen können z.B. Mitglieder von mehreren Vereinen sein. Man erkennt dies an der Multiplicity auf der Seite des Ganzen. 2.3.3.2.5.2 Realization Eine Klasse realisiert etwa ein Interface, d.h. sie implementiert das im Interface spezifizierte Verhalten. 2.3.3.2.5.3 Generalization Das spezifische Element enthält im Vergleich zum generellen Element zusätzliche Informationen. Eine Instanz des spezifischen Elementes kann an Stelle einer Instanz des generellen Elementes benutzt werden. Die spezifische Klasse erbt Attribute, Operationen und Assoziationen. (Vererbungs-Beziehung "is a") Generalization kann für Classes, Use Cases und Packages genutzt werden. ???Klassen und Funktionen können durch den Property String {abstract} gekennzeichnet werden. ???Polymorphismus kann nur durch Notes ausgedrückt werden. ???Constraint Generalizations Overlapping and Disjoint Generalizations Overlapping Generalization bedeutet, dass Mehrfache Vererbung mit gemeinsamer Basisklasse erlaubt ist. Disjoint Generalization erlaubt keine gemeinsame Basisklasse. ???Complete Generalization Mittels Constraint kann spezifiziert werden, dass keine weiteren Klassen abgeleitet werden dürfen. Eine Generalisierung drückt immer unterschiedliche Abstraktionslevel aus. 2.3.3.2.5.4 Dependency 109 110 Das Element ist von einem anderen abhängig. Beispiele für Abhängigkeiten : Operationen der Client Klasse haben eine Signatur mit Returnwert oder Parameter vom Typ der Server Klasse. Eine Klasse greift auf ein globales Objekt vom Typ einer anderen Klasse zu. Eine Klasse ruft eine Class Scope Operation einer anderen Klasse. Eine Elementfunktion einer Klasse definiert ein lokales Objekt einer anderen Klasse. In allen Fällen muss der Code der abhängigen Klasse modifiziert werden, wenn sich das Interface der verwendeten Klasse ändert. Diese Beziehung kann auf Classes, Use Cases, Packages angewandt werden. 2.3.3.2.6 Templates Ein Template ist eine Klasse, die nicht vollständig spezifiziert wurde. Die endgültige Spezifikation erfolgt über Parameter. Man spricht auch von einer parametrisierten Klasse. Eine parametrisierte Klasse kann ein Array sein. Die instanzierte Klasse kann dann ein Array von Autos oder ein Array von Farben sein. 2.3.3.3 Weitere Beispiele 110 111 Buch S. 45 Bild 3.5 Buch S. 47 Bild 3.6 + Text Text S. 47 unten Buch S. 185 Bild 8.8 Buch S. 188 Bild 8.12 + Text S. 189 111 112 Unterschied zwischen dem Requirement Domain Model und dem Analyse Model nach Rational : Die Klassen des Domain Models repräsentieren reale Objekte Die Klassen des Analyse Models repräsentieren Objekte des Computersystems 2.3.3.4 Erstellen der Use Case Realisierungen 2.3.3.4.1 Beispiel Getränkeautomat // Einfügen 2.3.3.4.2 Beispiel Bibliothek // Einfügen 2.3.3.4.3 Erstellen eines Use Case spezifischen Class Diagrams Das Use Case spezifische Class Diagram zeigt alle an dem Use Case partizipierende Klassen. 2.3.3.4.4 Erstellen eines Object Diagrams 2.3.3.4.4.1 Konzept Ein Object Diagram ist eine Abwandlung des Klassendiagramms. Im Unterschied zum Klassendiagramm zeigt es die Instanzen der Klassen und Instanzen der Associations. In diesem Sinne zeigt das Object Diagram einen Schnappschuss des Systems zu einem bestimmten Zeitpunkt während der Ausführung. Das Object Diagram nutzt mit zwei Ausnahmen dieselben Notationen wie das Class Diagram. Die Namen der Objekte werden unterstrichen und es werden alle Instanzen einer Beziehung dargestellt. Durch eine geeignet gewählte Folge von Zeitpunkten kann man daher die Statusänderungen eines Objektes darstellen. Der Status eines Objektes umfasst alle Attribute und die aggregierten Teile. Der Klassenname kann weggelassen werden und es können unbenannte Objekte einer Klasse dargestellt werden. 2.3.3.4.4.2 Associations Die Instanzen der Associations werden als Links bezeichnet. Ein Link spezifiziert ein Pfad entlang dem eine Message gesendet werden kann. Links können mit Stereotypen versehen werden. UML erlaubt für fast alle Model Elemente Typen und Instanzen darzustellen. Die Sichtbarkeit der Objekte kann näher beschrieben werden : Association Das korrespondierende Objekt ist sichtbar, weil eine Association bzgl. der Klassen existiert. (Das sind die wichtigsten Links, da sie die Struktur des Systems widerspiegeln !) Parameter 112 113 Das korrespondierende Objekt ist sichtbar, weil es ein Parameter ist. Local Das korrespondierende Objekt ist sichtbar, weil es eine lokale Variable ist. Global Das korrespondierende Objekt ist sichtbar, weil es eine globale Variable ist. Aktive Objekte, das sind Objekte, welche die Wurzel eines Kontrollflusses (Thread) repräsentieren, können durch einen fetten Rand markiert werden oder durch den Stereotyp <<Thread>>. 2.3.3.4.5 Erstellen eines Collaboration Diagram Das Collaboration Diagram zeigt den Nachrichtenaustausch im Kontext der Objekte und deren Beziehungen. Das Zusammenspiel ist eine grundlegende Designentscheidung ! Ein Objekt erhält eine Botschaft und sendet daraufhin eine Botschaft an sich selbst (Aufruf eigener Elementfunktion) bzw. an ein kooperierendes Objekt (Aufruf Elementfunktion von kooperierendem Objekt). Das Wissen über das Zusammenspiel verschiedener Objekte ist in keiner der beteiligten Klassen vollständig vorhanden. Der Mechanismus des Zusammenspiels ist ein Muster (Pattern), dessen Wiederverwendbarkeit meist höher ist als die einzelner Klassen. Innerhalb der Szenarien sollte man daher nach nicht trivialen Mechanismen suchen, die im Rahmen der Systemarchitektur dokumentiert werden können (Beschreibung der Mechanismen durch Sequence und Collaboration Diagrams). Üblicherweise werden unterschiedliche Ablaufmöglichkeiten in verschiedenen Collaboration Diagrams darsgestellt. Jedem Link kann eine Message mit einer Nummer zugeordnet werden. (Die Verschachtelung der Nachrichten kann durch eine Nummerierung analog zur Kapitelnummerierung ausgedrückt werden. 1.2 ist z.B. die zweite Message innerhalb der Behandlung der Message 1.) Die Erzeugung bzw. Vernichtung eines Objektes kann mittels new/create bzw. destroy/delete Messages oder Konstruktor-/Destruktoraufruf gekennzeichnet werden. Kontrollflüse können unterschieden werden, indem man dem Funktionaufruf den Namen des Threads voranstellt. 2.3.3.4.6 Erstellen eines Sequence Diagrams Ein Sequence Diagram zeigt den Nachrichtenaustausch zwischen den Objekten zeitlich auseinandergezogen entlang der vertikalen Lebenslinie der Objekte. Ein Sequence Diagram betont die Zeit (Focus : Time) und die Reihenfolge, ein Collaboration Diagram betont die Objekte und deren Beziehungen (Focus : Space). Sie repräsentiert die Existenz des Objektes zu einem bestimmten Zeitpunkt. Über die Lebenslinie lässt sich die Erzeugung oder Vernichtung eines Objektes darstellen. Der Empfang einer Nachricht aktiviert das empfangende Objekt. Ein aktiviertes Objekt führt entweder seinen eigenen Code aus oder wartet auf die Antwort eines anderen Objektes, dem es eine Nachricht gesendet hat. Die Aktivität eines Objektes wird durch ein vertikales dünnes Rechteck dargestellt. Man unterscheidet prinzipiell zwischen der Generic Form und der Instance Form eines Sequence Diagrams. 113 114 Die Instance Form beschreibt ein spezifisches Scenario im Detail. Sie dokumentiert eine mögliche Interaktion. Es gibt keine Bedingungen, Verzweigungen oder Schleifen. Die Generic Form beschreibt alle möglichen Alternativen in einem Scenario. Daher existieren Verzweigungen, Bedingungen und Schleifen. Iterationen lassen sich durch eine Bemerkung am Rand ausdrücken. Man kann auch die Message mit einem Prefix versehen, welches die Iteration [i := 1..n] oder die Bedingung [x > 0] für eine Verzweigung beschreibt. UML schreibt das Format nicht vor. Üblicherweise stellt man die verschiedenen Ablaufmöglichkeiten jedoch nicht in einem einzigen Sequenzdiagramm sondern in unterschiedlichen Sequenzdiagrammen dar. Messages können eine Signatur mit Parameter haben. Messages können Nummern haben. Man unterscheidet folgende Message Types Simple For messages with a single thread of control, one object sends a message to a passive object. Synchronous In synchronous messages, the operation proceeds only when the client sends a message to the supplier and the supplier accepts the message. The client runs until it sends the message; it then waits for the supplier to accept it. The client continues to wait until the message is accepted. Asynchronous Asynchronous communication occurs when the client sends a message to the supplier for processing and continues to execute its code without waiting for or relying on the supplier's receipt of the message. 2.3.3.5 Spezifizierung der Klassen 2.3.3.5.1 Beispiel Getränkeautomat Ausgabe Manager Diese Klasse ist verantwortlich für die komplette Steuerung einer Flaschenausgabe. Sie beinhaltet die für den Verkauf erforderliche Geschäftslogik. Insbesondere prüft sie, ob der eingegebene Geldbetrag für die selektierte Flasche ausreichend ist. Die Verbuchung von Geld und Flasche sowie die eigentliche Ausgabe delegiert sie an die entsprechenden Klassen. Die Klasse ist passiv. Es gibt nur eine Instanz dieser Klasse. Funktionsbeschreibungen: ... ... ... 2.3.3.5.2 Beispiel Bibliothek // Einfügen 2.3.3.5.3 Identifizieren von Elementfunktionen und Attributen 114 115 Wie findet man Elementfunktionen und Attribute ? Man betrachtet die Use Cases sowie die identifizierten Klassen und analysiert des Zusammenspiel der Objekte mittels Sequence- und Collaboration Diagrams. Aus den an die Instanzen der Klasse gesendeten Botschaften ergibt sich die öffentliche Schnittstelle der Klasse. Diese Funktionen werden durch Konstruktoren, Destruktoren und private Hilfsfunktionen ergänzt. Man notiert die zur Ausführung einer Funktion erforderlichen Informationen. Auf diese Weise ergeben sich Klassenattribute und Funktionsparameter. Man analysiert die möglichen Zustände der Bestandteile durch Betrachtung von State Diagrams (vgl. weiter unten). Diese führen zu States in Form von Attributwerten und zu Events und Actions in Form von Operationen. Im Rahmen des Designs sollten auch die Parameter der Operationen präzisiert werden. Dabei ergeben sich wiederum neue Klassen zur „Verpackung“ von Informationen für einen Transport. Anmerkungen Formalisierte Sprachen für den Entwurf Analyse und Entwurf sind genau so fehleranfällig wie die Implementation. Sie sind ggf. sogar fehleranfälliger, da sie weniger präzise (meist keine formale Sprache) spezifiziert und nicht wie die Implementation durch Werkzeuge überprüft werden können (z.B. Compiler). Durch eine strenge Formalisierung der Notation/Sprache kann hier eine Verbesserung erzielt werden (z.B. Entwurfsformulierung mit DLCA Defined Language of Cooperating Automata). Jedoch muß der Formalismus dem praktischen Problem angemessen sein und er darf den mathematischen Background der beteiligten Designer und Programmierer nicht übersteigen Manchmal ist es sinnvoll, logisch zusammengehörende und in vielen Objekten benutzte Hilfsfunktionen in einer eigenen Service-Klasse (ohne Daten !) zusammenzufassen, z.B. Funktionen zum Sortieren oder zur Umrechnung vom Dezimal- ins Hexadezimalsystem. Man schreibe grundsätzlich Elementfunktionen für Konstruktion u. Destruktion (auch CopyKonstruktor !) und Zuweisungsoperatoren (operator=()). Man schreibe ggf. Vergleichsoperatoren (operator==(), operator!=()). Funktionen zum Auslesen- bzw. Setzen der Zustandsdaten eines Objektes (Selektor bzw. Modifizierer). Enthält ein Objekt viele Elementobjekte vom gleichen Typ, benötigt man einen Iterator, der es erlaubt, auf alle Elementobjekte in einer wohdefinierten Reihenfolge zuzugreifen. Für den Zugriff auf die Daten sind prinzipiell Funktionen zu benutzen. Ist die Menge der über die Funktionsschnittstelle zu transportierenden Daten zu groß, sollte trotzdem kein direkter Zugriff auf die Daten gewährt werden. Stattdessen sollte man Iteratoren zur Verfügung stellen, die es ermöglichen, sich in der Menge zu bewegen. Ferner werden dann Funktionen zum Einfügen, Löschen, Editieren benötigt. Wenn man davon ausgeht, daß die Daten in einem nicht zugreifbaren Speicher (z.B. Datenbank) verwaltet werden, können keine Zeiger auf die zu editierenden Daten übergeben werden. Das Editieren ist nur möglich, indem man die Daten als Parameter übergibt oder vor dem edit() Aufruf eine im eigenen Speicher zugreifbare Kopie editiert (Konzept von CRecordset). Die Kopie sollte Teil des Objektes sein, welches die Datenmenge repräsentiert. Kommunikation zwischen Ansichten und Dokumentenobjekten sollte nicht über den "Dienstweg" erfolgen (Objektbaum hoch und wieder runter). Man müßte sonst im obersten Objekt (Dokument) sämtliche Nachrichten an die untergeordneten Objekte (z.B. Testfall) weitergeben können. D.h. aber, daß alle Schnittstellenelementfunktionen der untern Objekte auch Schnittstellenlementfunktionen des obersten Objektes sein müssen, da Nachrichten nur über Elementfunktionen ausgetauscht werden können. Daher baut man besser Assoziationen 115 116 zwischen untergeordneten Objekten auf (z.B. könnte das Testfallansichtsobjekt einen Zeiger auf das aktuelle Testfalldatenobjekt erhalten) und ermöglicht so den direkten Nachrichtenaustausch. Wir ergänzen private nicht in der Schnittstelle enthaltene Hilfsfunktionen, wie etwa Prüfroutinen (index im erlaubten Bereich, auto in der gewünschten Farbe darstellbar usw.) , Funktionen zur Vereinfachung der Notation (gebeDatumAus()). Wir unterscheiden ggf zwischen allgemeinen Anwendern einer Klasse und erbenden Anwendern durch spezifizieren von protected Anteilen an der öffentlichen Schnittstelle. Wir legen die Argumenttypen der Schnittstellenfunktionen fest. Dabei ist darauf zu achten, daß alle Operationen einer Klasse dasselbe Abstraktionsniveau unterstützen und daß dieses zum Abstraktionsniveau der Klasse paßt. Beispiele für typische Entwurfsentscheidungen : Soll z.B. in einer Klasse cZylinder das Volumen gespeichert o. jeweils berechnet werden. Sollen z.B. Flugplaninformationen bzgl. der Suche oder dem Löschen/Einfügen optimiert werden. Implementierung von Statusmaschinen durch Darstellung jedes Status durch ein Objekt einer den Status repräsentierenden Klasse. Diese Objekte verhalten sich bedingt durch Polymorhie unterschiedlich und lösen damit unterschiedliche Aktionen aus. 2.3.3.5.4 Beschreibung der Verantwortlichkeiten der Klassen Die Bedeutung der im Diagramm dargestellten Klassen muss für den Leser deutlich werden. Daher ist die Verantwortlichkeit jeder Klasse textuell durch einige Sätze zu beschreiben. Die grobe Verantwortlichkeit einer Klasse kann bereits durch den Namen deutlich werden, wenn ein entsprechender Klassenbegriff in der Umgangssprache vorliegt. Zusätzlich müssen charakteristische Eigenschaften verbal oder durch die Angabe von einigen Attributen oder Elementfunktionen (Schnittstellenfunktionen, keine privaten Hilfsfunktionen) beschrieben werden. Ferner wird im Rahmen der Klassenspezifikation angegeben : Basisklassen Zustandsdiagramm (vgl. weiter hinten) Anzahl der möglichen Instanzen Multithreading Verhalten Aktive Klasse 2.3.3.5.5 Erstellen der Beschreibungen der Schnittstellenfunktion Die Funktionsbeschreibungen kann man z.B. untergliedern nach Konstruktoren, Destruktoren Gruppen von public Functions Safe&Load Functions Die Funktionsbeschreibung einer Methode sollte enthalten Funktionskopf mit Signatur Return Value Parameters Exceptions Specifier const, virtual, abstract Multithreading Verhalten Remarks 116 117 Im Rahmen der Properties für eine Operation können z.B. darüber hinaus Preconditions, Postconditions oder Zustandsänderungen formuliert werden. Wenn die Parameter der Elementfunktionen bereits spezifiziert sind, sollte man nach Gemeinsamkeiten suchen. Ggf. kann man gemeinsame Klassen für gewisse Parameter einführen. Im Falle von Klassenkategorien stellen die Operationen die Services dar, die von der Kategorie exportiert werden und die letztendlich von vielen Klassen der Kategorie realisiert werden. Der Unterschied zwischen Verhalten (Funktion) und Eigenschaft (Attribut) ist oft nur schwer erkennbar. Z.B. kann man die Tatsache, daß ein Lkw beladen werden kann durch ein Attribut "LadungInTonnen" oder eine Elementfunktion laden() ausdrücken. Beispiel für das Zusammenspiel verschiedener Objekte : Im Rahmen eines Updates wird jede Ansicht aufgefordert, die Daten darzustellen. Jede Ansicht erfragt die Daten des Dokumentes, bildet ein grafisches Modell und fordert dieses auf sich zu zeichnen. Document und View liegen auf demselben Level. Es handelt sich hier also nicht um Aufträge an ein untergeordnetes Objekt. 2.3.3.5.6 Updaten der Beziehungen zwischen den Klassen Einrichten von Vererbungsbeziehungen Werden z.B. Operationen oder Attribute von mehreren gleichberechtigten Klassen benötigt, sollten sie in einer ggf. abstrakten Oberklasse untergebracht werden. An dieser Stelle findet ein Teil der Klassifizierung statt. Einrichten der Associations Die für den Botschaftsverkehr notwendigen Assoziationen müssen eingerichtet werden. Diese Associations werden dann ggf. zu Aggregations oder Composite Aggregations verfeinert. Es werden Rollen, eventueller Einschränkungen und Kardinalitäten notiert. Achtung : Vererbungsbeziehungen sind keine Botschaftswege (Basis- u. abgeleitete Klasse stehen nicht für verschiedene Objekte). 2.3.3.5.7 Statechart Diagram 2.3.3.5.7.1 Beispiel Getränkeautomat // Einfügen 2.3.3.5.7.2 Beispiel Bibliothek // Einfügen 2.3.3.5.7.3 Konzept Ein Statechart-Diagram zeigt alle möglichen Zustände eines Objektes und welche Ereignisse zustandsänderungen bewirken. 2.3.3.5.7.4 States 117 118 Alle Objekte haben einen Status. Dieser ist bestimmt durch die Werte der Attribute (einschließlich der Links zu anderen Objekten). Jede Änderung eines Attributwertes ist im Prinzip eine Statusänderung. State Diagrams werden nur für solche Klassen gezeichnet, die eine gewisse Anzahl wohldefinierter Zustände besitzen, welche das Verhalten der Klasse beeinflussen. Es gibt einen Initial State und ggf. mehrere Final States. In UML besitzt jeder State einen Namen. Optional können Attributwerte, Events und Actions angegeben werden. Durch Schachtelung können Substates dargestellt werden. Der umfassende State wird als Composite State bezeichnet. Mittels History Indicator können Substates gespeichert werden. 2.3.3.5.7.5 Events Events sind Trigger, die Zustandsübergänge aktivieren. Der Empfang eines Signals, welches selbst ein Objekt ist oder der Aufruf einer Operation durch ein anderes Objekt wird durch eine Event-Signature an einer Transition dargestellt. Events können mit Parameter versehen werden (Event Signature). Der Ablauf einer Zeitperiode wird durch einen Zeitausdruck an einer Transition gekennzeichnet. 2.3.3.5.7.6 Transitions Die Zustandsänderung wird als Transition bezeichnet. Einer State Transition ist normalerweise ein Event zugeordnet. In diesem Falle wird die Transition bein Auftreten des Events durchgeführt. Wenn kein Event zugeordnet ist, wird die Transition ausgeführt, wenn die internen Actions des Source States beendet sind. Guard Condition Eine Guard Condition ist ein Boolscher Ausdruck. Eine Transition, welche mit diesem Ausdruck versehen ist, „feuert“, wenn der Ausdruck wahr ist. Falls eine Transition mit Guard Condition und Event gekennzeichnet ist, muss der Ausdruck wahr und der Event eingetreten sein. Action Expression Mittels Action Expressions können Transitions mit Operationen oder Attributzuweisungen versehen werden. Für Wertzuweisungen wird := verwendet. Die Anweisungen sind durch / getrennt. Eine Action ist eine ausführbare atomare Berechnung. Hierunter fällt z.B. der Aufruf einer Funktion, die Erzeugung eines Objektes oder das Senden eines Signals an ein anderes Objekt. Atomar bedeutet, dass die Berechnung nicht durch einen Event unterbrochen werden kann. Innerhalb eines States kann mittels der Standardevents Entry, Exit spezifiziert werden, welche Actions jeweils beim Eintritt in den Zustand und beim Austritt aus dem Zustand durchzuführen sind. Die Spezifizierung einer Action mittels Entry erspart es, jede Transition, die in den betreffenden Zustand führt, mit dieser Action zu kennzeichnen. 2.3.3.5.8 Activity Diagram 118 119 (Eriksson S. 23 Bild 2.8) 2.3.3.5.8.1 Beispiel Getränkeautomat // Einfügen 2.3.3.5.8.2 Konzept Eine Activity besteht aus einer Sequenz von Actions und ist durch einen Event unterbrechbar. Eine Activity kann z.B. eine längere Berechnung oder das Pollen einer Queue sein. Ein Statchart Diagram betont die Zustände eines Objektes und zeigt die mit den Zustandsübergängen und Zuständen verbundenen Aktivitäten nur nebenbei. Ein Activity Diagram betont hingegen die Aktivitäten. Man kann ein Activity Diagramm als Spezialfall eines Statchart Diagrams ansehen, in welchem alle Aktivität gewissen States zugeordnet sind und alle Transitions durch die Beendigung der Aktivität getriggert werden. Activity Diagrams zeigen den Kontrollfluss von Aktivität zu Aktivität (Focus : Work). 2.3.3.5.9 Identifizierung aktiver Klassen Falls z.B. einer der Actors hohe Performance Ansprüche hat, sollte er durch ein eigenes aktives Objekt bedient werden. Eine Verteilung auf mehrere Rechner erfordert zumindest ein aktives Objekt pro Rechner und separate aktive Objekte für die Rechnerkommunikation. In diesem Zusammenhang muss dann z.B. die Kommunikation der aktiven Objekte, die Synchronisartion, die Vermeidung von Deadlocks und Starvation sowie die Erzeugung und Terminierung bedacht werden (vgl. Multithreading Vorlesung SE2). 2.3.3.6 2.3.3.6.1 Erstellen eines Package Diagrams Beispiel Getränkeautomat 119 120 GUI Geldmanageme nt Automatenman agement Persistenzmanagement Getränkemanag ement Utility Devices 2.3.3.6.2 Beispiel Bibliothek GUI Büchermanage ment Ausleihmanage ment Lesermanagem ent Devices 2.3.3.6.3 Packages Bei allen hinreichend großen Systemen können wir Klassengruppen erkennen, die in sich selbst stark aber mit anderen Gruppen nur lose gekoppelt sind, die semantisch eng zusammen gehören und meist nur gemeinsam geändert werden können. Z.B. könnte man die Java Oberfläche durch eine Windows-Oberfläche ersetzen wollen. Daher gruppieren wir z.B. alle Oberflächen-Klassen in einem eigenen Package. Packages dienen dazu, das logische Modell eines Systemes aufzuteilen. 120 121 Ein Package ist ein Mechanismus zum Gruppieren. Im Hinblick auf den begrenzten Überblick eines Menschen sollte die Anzahl der in einem Package zusammenarbeitenden Klassen oder Packages nicht deutlich über 7 liegen. Experimente des Psychologen Miller stützen dies (Begründung duch Leistungsvermögen Kurzzeitgedächtnis). 2.3.3.6.4 Beziehungen zwischen Packages Packages können in einer Dependency, Genaralization Beziehung zueinander stehen. Die Abhängigkeit drückt bei Packages die Importbeziehung aus. Die Klassen der im "Importbaum" obenliegenden Packages importieren Klassen der darunterliegenden Packages. Vererbung hat etwa dieselbe Bedeutung wie bei Klassen. Z.B. spezialisiert das Package WindowsGUI das Package GUI. Das spezialisierte Package erbt damit die public und protected Elemente des verallgemeinerten Packages. Wie bei Klassen kann das spezialisierte Package dabei Elemente ersetzen und neue hinzufügen. Ein spezialisiertes Package kann überall dort verwendet werden, wo ein allgemeines Package erwartet wird. Ein Package besitzt seine Model Elemente. Ein Model Element kann nicht in mehr als einem Package enthalten sein. Packages können jedoch Model Elements von anderen Packages importieren. 2.3.3.6.5 Zugriffssteuerung Der Zugriff auf den Inhalt eines Package kann durch private, protected, public und implementation gekennzeichnet werden. Im Falle von protected können nur Kinder des Packages das Model Element sehen. Private Elemente sind außerhalb des Packages nicht sichtbar. Damit hat man eine klare Trennung zwischen den exportierten public Klassen, die das Interface des Package stellen, und den private Klassen, die die Services implementieren. Exportierte Elemente sind nur in solchen Packages sichtbar, die das exportierende Package explizit importieren. Die Importbeziehung ist nicht transitiv. Wenn ein Element in einem Package sichtbar ist, so ist es in allen eingeschachtelten Packages sichtbar. Eingeschachtelte Packages sehen alles, was das umgebende Package sieht. Ein Package stellt einen Namensraum dar. Verschiedene Arten von Model Elementen können denselben Namen haben, jedoch darf es z.B. keine zwei Klassen mit demselben Namen geben. 2.3.3.7 2.3.3.7.1 Erstellen des Deployment Diagrams Beispiel Getränkeautomat 121 122 <<Windows2000>> Automaten Controller <<CanBus>> Geldein-/aus geber <<CanBus>> Flaschenaus geber <<Internet>> <<CanBus>> <<Linux>> Zentraler Monitor 2.3.3.7.2 Flaschenein sammler Beispiel Bibliothek BibliothekApplication TransportSystemCo ntroller TransportSy stem EingabeAuto mat <<Linux>> HTML-Client <<Windows>> SwingClient AusgabeAut omat 122 123 2.3.3.7.3 Konzept Deployment Diagrams zeigen die physikalische Verteilung der Hard- und Software des Systems zusammen mit deren Verbindungen. Es wird erkennbar, welche Software Component (vgl. weiter unten) auf welchem Gerät ausgeführt wird. Nodes Nodes sind physikalische Objekte wie Computer, Drucker, Kartenleser usw. Sie repräsentieren die Hardware, auf welchen die Components ausgeführt werden. Ein Node kann als Typ (Klasse) und als Instanz (Objekt) dargestellt werden. Die Klasse beschreibt etwa die Charakteristik eines Processors und das Objekt repräsentiert einen konkreten Processor dieses Typs. Die Eigenschaften (Prozessorgeschwindigkeit oder der vorhandene Speicher) können im Rahmen der Klassenattribute oder der Properties beschrieben werden. Im Falle von Klassen, können auch Vererbungsbeziehungen dargestellt werden. Im Falle einer Instanz wird der Name unterstrichen. Communication Association Nodes werden mittels Communication Association miteinander verbunden. Mittels Stereotyp wird das Netzwerk oder das Protokoll gekennzeichnet. Executable Components Die Instanzen der ausgeführten Programme können in den Instanzen der Nodes aufgeführt werden. Objects Instanzen von Klassen, also Objects, können Instanzen von Nodes zugeordnet werden. Z.B. kann ein Passive Object innerhalb eines Active Objects und dieses innerhalb einer Executable Component dargestellt werden. 2.3.3.8 Unterschied zwischen Analyse und Design 2.3.3.8.1 Analyse Booch „Unsere Erfahrung in der Entwicklung großer Systeme hat gezeigt, dass die zu Anfang erfolgte Spezifizierung der Anforderungen niemals vollständig ist, oft nur ungenau und immer im Widerspruch zu sich selbst steht.“ OOA nach Booch : „Die objektorientierte Analyse ist eine Analysemethode, welche die Anforderungen aus der Perspektive der Klassen und Objekte, die sich im Vokabular des Problembereichs finden, betrachtet.“ Die Analyse beschreibt, "was" das System tut, nicht "wie" es das tut. Hierzu gehört die Identifikation von wichtigen Klassen und Objekten des Problembereichs und die Beschreibung der UseCases aus der Sicht der kollaborierenden Objekte. Das bisher monolithische System splittet auf in Analysis Level Objekte. Damit wird auch das Problem eingegrenzt, indem festgelegt wird, was von Interesse ist und was nicht. Das Analysis Model ist noch frei von technischen Details. Es vermittelt daher eine sehr abstrakte Sichtweise der Zerlegung des Systems. Das Analysis Model liefert ein genaues Verständnis der Anforderungen. 123 124 2.3.3.8.2 Design Das Design Model beschreibt, "wie" das System die gestellten Anforderungen erfüllt. In Rahmen des Design treffen Architekten die grundlegenden architektonischen und strategischen Entscheidungen. Sie eignen sich dabei ein Verständnis der verwendeten Technologien wie Betriebssystem, GUI, Datenbank usw. an. Sie zerlegen das System in kleinere von ggf. unterschiedlichen Teams bearbeitbare Teile und definieren die Interfaces. Die UML Definition der Architektur Architecture is the organizational structure of a system. An architecture can be recursively decomposed into parts that interact through interfaces, relationships that connect parts, and constraints for assembling parts. Das Analysis Model liefert eine gewisse dem Problembereich angepasste Grundstruktur. Diese meist an den realen Objekten orientierte Grundstruktur sollte man im Design Model nach Möglichkeit beibehalten. 2.3.3.9 Weitere Beispiele : School software Engineering : : Student peterEdelmann : Instructor Course( ) hire( ) enrol( ) visit( ) listenTo( ) 124 125 : School G 3: enrol( ) 4: visit( ) : Student 1: Course( ) softwareEngineering : Course P F 2: hire( ) peterEdelmann : Instructor 5: listenTo( ) 125 126 Activ e cardInserted Idle Validating Selecting maintain Processing Cancel Maintenance TestingDev ices Printing Self Diagnosis Idle tooHot( desiredTemp ) atTemp / shutDown tooCold( desiredTemp )[ time > 7.00 ] atTemp / shutDown Heating Cooling Activ ating Activ e 126 127 kernel32.dll <<DOCUMENT>> animator.hlp <<DLL>> dlog.dll <<EXE>> animator. exe <<FILE>> animator.i ni <<DLL>> render.dll <<TABLE>> shapes.tbl render.cpp render.h <<Console>> salesA <<Net>> Internet server <<satelliteLink>> <<Console>> <<Ethernet>> salesB Printer Console 127 128 2.3.4 Implementation 2.3.4.1 Erstellen des Component Diagrams 2.3.4.1.1 Beispiel Getränkeautomat CKunden Ansicht CGetränke Manager 2.3.4.1.2 CAusgabe Manager CGeldMan ager Beispiel Bibliothek // Einfügen 2.3.4.1.3 Konzept Ein Component Diagram stellt die physikalische Realisierung von logischen Modellelementen und ihre Abhängigkeiten dar. Es zeigt damit die physikalische Struktur des Codes. Eine Dependency Relation zwischen Components drückt aus, dass eine Component die andere für ihre vollständige Definition benötigt. In einer compilierten Sprache erfordert die Modifikation der einen Component die Recompilierung der anderen Component. Im Falle einer Executable Component kann die Dependency Relation verwendet werden, um auszudrücken, welche DLL das Programm benötigt. Components sind Typen. Nur Executable Components können Instanzen haben. Eine solche Instanz wird auf einem Processor ausgeführt. Mittels Stereotyp kennzeichnet man die Components genauer, z.B. : <<file>> Source Code File <<page>> Web Seite <<document>> Dokument (nicht compilierbar) Binary Component Object Code als Resultat des Compilierens. Executable Component Ein ausführbares File als Resultat des Linkens. Sie repräsentiert die ausführbare Einheit, die auf einem Processor läuft. <<executable>> Ausführbares Programm <<library>> Static or dynamic object library <<table >> Datenbank 128 129 2.3.4.2 Erstellen des Integration Build Plan 2.3.4.2.1 Beispiel Getraenkeautomat GUI (from Automat) AutomatenMana gement (from Automat) VollFlaschenMan agement (from Automat) Persistence (from Automat) LeerFlaschenMa nagement (from Automat) GeldManagemen t (from Automat) Utility Devices (from Automat) 2.3.4.2.2 Bottom Up Integration Man identifiziert diejenigen Subsystems, die an den zu implementierenden Use Cases partizipieren. Man definiert “Build Sets”, das sind Subsystems, die aus Integrationssicht zusammengehören. 129 130 Man definiert eine Serie von Builds (typischerweise Bottom Up) // Im Beispiel Einfügen 130 131 2.3.4.3 Implementieren der Components 2.3.4.3.1 Ziel Es werden alle Components produziert, die für ein ausführbares System erforderlich sind. Insbesondere werden die Design Klassen in File Components mit Source Code und schließlich in Binärfiles bzw. Executables umgesetzt. Es werden zu den Packages korrespondierende Verzeichnisse angelegt. 2.3.4.3.2 Programmierregeln 2.3.4.3.2.1 Java Code Conventions (Selbststudium) Vgl. Java-Codeconvention.pdf Java Code Conventions 1 - Introduction 1.1 Why Have Code Conventions Code conventions are important to programmers for a number of reasons: • 80% of the lifetime cost of a piece of software goes to maintenance. • Hardly any software is maintained for its whole life by the original author. • Code conventions improve the readability of the software, allowing engineers to understand new code more quickly and thoroughly. • If you ship your source code as a product, you need to make sure it is as well packaged and clean as any other product you create. For the conventions to work, every person writing software must conform to the code conventions. Everyone. 1.2 Acknowledgments This document reflects the Java language coding standards presented in the Java Language Specification, from Sun Microsystems, Inc. Major contributions are from Peter King, Patrick Naughton, Mike DeMoney, Jonni Kanerva, Kathy Walrath, and Scott Hommel. This document is maintained by Scott Hommel. Comments should be sent to [email protected] 2 - File Names This section lists commonly used file suffixes and names. 2.1 File Suffixes Java Software uses the following file suffixes: File Type Java source Java bytecode File Name GNUmakefile software. Suffixes .java Java bytecode 2.2 Common File Names Frequently used file names include: Use The preferred name for makefiles. We use gnumake to build our 131 132 README particular directory. The preferred name for the file that summarizes the contents of a 3 - File Organization A file consists of sections that should be separated by blank lines and an optional comment identifying each section. Files longer than 2000 lines are cumbersome and should be avoided. For an example of a Java program properly formatted, see “Java Source File Example” on page 18. 3.1 Java Source Files Each Java source file contains a single public class or interface. When private classes and interfaces are associated with a public class, you can put them in the same source file as the public class. The public class should be the first class or interface in the file. Java source files have the following ordering: • Beginning comments (see “Beginning Comments” on page 2) • Package and Import statements • Class and interface declarations (see “Class and Interface Declarations” on page 3) 3.1.3 Class and Interface Declarations All source files should begin with a c-style comment that lists the class name, version information, date, and copyright notice: /* * Classname * * Version information * * Date * * Copyright notice */ 3.1.2 Package and Import Statements The first non-comment line of most Java source files is a package statement. After that, import statements can follow. For example: package java.awt; import java.awt.peer.CanvasPeer; 3.1.3 Class and Interface Declarations The following table describes the parts of a class or interface declaration, in the order that they should appear. See “Java Source File Example” on page 18 for an example that includes comments. Part of Class/Interface Declaration Class/interface documentation comment (/**...*/) class or interface statement Class/interface implementation comment (/*...*/), if necessary Notes See “Documentation Comments” on page 8 for information on what should be in this comment. This comment should contain any class-wide or interface-wide information that wasn’t appropri-ate for the class/interface documentation com-ment. 132 133 Class (static) variables Instance variables. First the public class variables, then the protected, then package level (no access modifier), and then the private. First public, then protected, then package level (no access modifier), and then private Constructors Methods These methods should be grouped by functional-ity rather than by scope or accessibility. For example, a private class method can be in between two public instance methods. The goal is to make reading and understanding the code eas-ier. 4 - Indentation Four spaces should be used as the unit of indentation. The exact construction of the indentation (spaces vs. tabs) is unspecified. Tabs must be set exactly every 8 spaces (not 4). 4.1 Line Length Avoid lines longer than 80 characters, since they’re not handled well by many terminals and tools. Note: Examples for use in documentation should have a shorter line length—generally no more than 70 characters. 4.2 Wrapping Lines When an expression will not fit on a single line, break it according to these general principles: Break after a comma. Break before an operator. Prefer higher-level breaks to lower-level breaks. Align the new line with the beginning of the expression at the same level on the previous line. If the above rules lead to confusing code or to code that’s squished up against the right margin, just indent 8 spaces instead. Here are some examples of breaking method calls: someMethod(longExpression1, longExpression2, longExpression3,longExpression4, longExpression5); var = someMethod1(longExpression1, someMethod2(longExpression2, longExpression3)); Following are two examples of breaking an arithmetic expression. The first is preferred, since the break occurs outside the parenthesized expression, which is at a higher level. longName1 = longName2 * (longName3 + longName4 - longName5) + 4 * longname6; // PREFER longName1 = longName2 * (longName3 + longName4 - longName5) + 4 * longname6; // AVOID Following are two examples of indenting method declarations. The first is the conventional case. The second would shift the second and third lines to the far right if it used conventional indentation, so instead it indents only 8 spaces. //CONVENTIONAL INDENTATION someMethod(int anArg, Object anotherArg, String yetAnotherArg, 133 134 Object andStillAnother) { ... } //INDENT 8 SPACES TO AVOID VERY DEEP INDENTS private static synchronized horkingLongMethodName(int anArg, Object anotherArg, String yetAnotherArg, Object andStillAnother) { ... } Line wrapping for if statements should generally use the 8-space rule, since conventional (4 space) indentation makes seeing the body difficult. For example: //DON’T USE THIS INDENTATION if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { //BAD WRAPS doSomethingAboutIt(); //MAKE THIS LINE EASY TO MISS } //USE THIS INDENTATION INSTEAD if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } //OR USE THIS if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } Here are three acceptable ways to format ternary expressions: alpha = (aLongBooleanExpression) ? beta : gamma; alpha = (aLongBooleanExpression) ? beta : gamma; alpha = (aLongBooleanExpression) ? beta : gamma; 5 - Comments Java programs can have two kinds of comments: implementation comments and documentation comments. Implementation comments are those found in C++, which are delimited by /*...*/, and //. Documentation comments (known as “doc comments”) are Java-only, and are delimited by /**...*/. Doc comments can be extracted to HTML files using the javadoc tool. Implementation comments are means for commenting out code or for comments about the particular implementation. Doc comments are meant to describe the specification of the code, from an implementation-free perspective to be read by developers who might not necessarily have the source code at hand. Comments should be used to give overviews of code and provide additional information that is not readily available in the code itself. Comments should contain only information that is 134 135 relevant to reading and understanding the program. For example, information about how the corresponding package is built or in what directory it resides should not be included as a comment. Discussion of nontrivial or nonobvious design decisions is appropriate, but avoid duplicating information that is present in (and clear from) the code. It is too easy for redundant comments to get out of date. In general, avoid any comments that are likely to get out of date as the code evolves. Note: The frequency of comments sometimes reflects poor quality of code. When you feel compelled to add a comment, consider rewriting the code to make it clearer. Comments should not be enclosed in large boxes drawn with asterisks or other characters. Comments should never include special characters such as form-feed and backspace. 5.1 Implementation Comment Formats Programs can have four styles of implementation comments: block, single-line, trailing and end-of-line. 5.1.1 Block Comments Block comments are used to provide descriptions of files, methods, data structures and algorithms. Block comments may be used at the beginning of each file and before each method. They can also be used in other places, such as within methods. Block comments inside a function or method should be indented to the same level as the code they describe. A block comment should be preceded by a blank line to set it apart from the rest of the code. /* * Here is a block comment. */ Block comments can start with /*-, which is recognized by indent(1) as the beginning of a block comment that should not be reformatted. Example: /** Here is a block comment with some very special * formatting that I want indent(1) to ignore. * * one * two * three */ Note: If you don’t use indent(1), you don’t have to use /*- in your code or make any other concessions to the possibility that someone else might run indent(1) on your code. See also “Documentation Comments” on page 8. 5.1.2 Single-Line Comments Short comments can appear on a single line indented to the level of the code that follows. If a comment can’t be written in a single line, it should follow the block comment format (see section 5.1.1). A single-line comment should be preceded by a blank line. Here’s an example of a single-line comment in Java code: if (condition) { /* Handle the condition. */ ... } 5.1.3 Trailing Comments Very short comments can appear on the same line as the code they describe, but should be shifted far enough to separate them from the statements. If more than one short comment appears in a chunk of code, they should all be indented to the same tab setting. Here’s an example of a trailing comment in Java code: 135 136 if (a == 2) { return TRUE; /* special case */ } else { return isPrime(a); /* works only for odd a */ } 5.1.4 End-Of-Line Comments The // comment delimiter can comment out a complete line or only a partial line. It shouldn’t be used on consecutive multiple lines for text comments; however, it can be used in consecutive multiple lines for commenting out sections of code. Examples of all three styles follow: if (foo > 1) { // Do a double-flip. ... } else{ return false; // Explain why here. } //if (bar > 1) { // // // Do a triple-flip. // ... //} //else{ // return false; //} 5.2 Documentation Comments Note: See “Java Source File Example” on page 18 for examples of the comment formats described here. For further details, see “How to Write Doc Comments for Javadoc” which includes information on the doc comment tags (@return, @param, @see): http://java.sun.com/products/jdk/javadoc/writingdoccomments.html For further details about doc comments and javadoc, see the javadoc home page at: http://java.sun.com/products/jdk/javadoc/ Doc comments describe Java classes, interfaces, constructors, methods, and fields. Each doc comment is set inside the comment delimiters /**...*/, with one comment per class, interface, or member. This comment should appear just before the declaration: /** * The Example class provides ... */ public class Example { ... Notice that top-level classes and interfaces are not indented, while their members are. The first line of doc comment (/**) for classes and interfaces is not indented; subsequent doc comment lines each have 1 space of indentation (to vertically align the asterisks). Members, including constructors, have 4 spaces for the first doc comment line and 5 spaces thereafter. If you need to give information about a class, interface, variable, or method that isn’t appropriate for documentation, use an implementation block comment (see section 5.1.1) or single-line (see section 5.1.2) comment immediately after the declaration. For example, details about the implementation of a class should go in in such an implementation block comment following the class statement, not in the class doc comment. Doc comments should not be positioned inside a method or constructor definition block, 136 137 because Java associates documentation comments with the first declaration after the comment. 6 – Declarations 6.1 Number Per Line One declaration per line is recommended since it encourages commenting. In other words, int level; // indentation level int size; // size of table is preferred over int level, size; Do not put different types on the same line. Example: int foo, fooarray[]; //WRONG! Note: The examples above use one space between the type and the identifier. Another acceptable alternative is to use tabs, e.g.: int level; // indentation level int size; // size of table Object currentEntry; // currently selected table entry 6.2 Initialization Try to initialize local variables where they’re declared. The only reason not to initialize a variable where it’s declared is if the initial value depends on some computation occurring first. 6.3 Placement Put declarations only at the beginning of blocks. (A block is any code surrounded by curly braces “{” and “}”.) Don’t wait to declare variables until their first use; it can confuse the unwary programmer and hamper code portability within the scope. void myMethod() { int int1 = 0; // beginning of method block if (condition) { int int2 = 0; // beginning of "if" block ... } } The one exception to the rule is indexes of for loops, which in Java can be declared in the for statement: for (int i = 0; i < maxLoops; i++) { ... } Avoid local declarations that hide declarations at higher levels. For example, do not declare the same variable name in an inner block: int count; ... myMethod() { if (condition) { int count; // AVOID! ... } ... } 137 138 6.4 Class and Interface Declarations When coding Java classes and interfaces, the following formatting rules should be followed: • No space between a method name and the parenthesis “(“ starting its parameter list • Open brace “{” appears at the end of the same line as the declaration statement • Closing brace “}” starts a line by itself indented to match its corresponding opening statement, except when it is a null statement the “}” should appear immediately after the “{“ class Sample extends Object { int ivar1; int ivar2; Sample(int i, int j) { ivar1 = i; ivar2 = j; } int emptyMethod() {} ... } • Methods are separated by a blank line 7 - Statements 7.1 Simple Statements Each line should contain at most one statement. Example: argv++; // Correct argc++; // Correct argv++; argc--; // AVOID! 7.2 Compound Statements Compound statements are statements that contain lists of statements enclosed in braces “{ statements }”. See the following sections for examples. The enclosed statements should be indented one more level than the compound statement. The opening brace should be at the end of the line that begins the compound statement; the closing brace should begin a line and be indented to the beginning of the compound tatement. Braces are used around all statements, even single statements, when they are part of a control structure, such as a if-else or for statement. This makes it easier to add statements without accidentally introducing bugs due to forgetting to add braces. 7.3 return Statements A return statement with a value should not use parentheses unless they make the return value more obvious in some way. Example: return; return myDisk.size(); return (size ? size : defaultSize); 7.4 if, if-else, if else-if else Statements The if-else class of statements should have the following form: if ( condition) { statements; } if ( condition) { statements; } else { statements; } if ( condition) { statements; } else if ( condition) { statements; } else { 138 139 statements; } Note: if statements always use braces {}. Avoid the following error-prone form: if ( condition) //AVOID! THIS OMITS THE BRACES {}! statement; 7.5 for Statements A for statement should have the following form: for ( initialization; condition; update) { statements; } An empty for statement (one in which all the work is done in the initialization, condition, and update clauses) should have the following form: for ( initialization; condition; update); When using the comma operator in the initialization or update clause of a for statement, avoid the complexity of using more than three variables. If needed, use separate statements before the for loop (for the initialization clause) or at the end of the loop (for the update clause). 7.6 while Statements A while statement should have the following form: while ( condition) { statements; } An empty while statement should have the following form: while ( condition); 7.7 do-while Statements A do-while statement should have the following form: do { statements; } while ( condition); 7.8 switch Statements A switch statement should have the following form: switch ( condition) { case ABC: statements; /* falls through */ case DEF: statements; break; case XYZ: statements; break; default: statements; break; } Every time a case falls through (doesn’t include a break statement), add a comment where the break statement would normally be. This is shown in the preceding code example with the /* falls through */ comment. Every switch statement should include a default case. The break in the default case is 139 140 redundant, but it prevents a fall-through error if later another case is added. 7.9 try-catch Statements A try-catch statement should have the following format: try { statements; } catch (ExceptionClass e) { statements; } A try-catch statement may also be followed by finally, which executes regardless of whether or not the try block has completed successfully. try { statements; } catch (ExceptionClass e) { statements; } finally { statements; } 8 - White Space 8.1 Blank Lines Blank lines improve readability by setting off sections of code that are logically related. Two blank lines should always be used in the following circumstances: Between sections of a source file Between class and interface definitions One blank line should always be used in the following circumstances: Between methods Between the local variables in a method and its first statement Before a block (see section 5.1.1) or single-line (see section 5.1.2) comment Between logical sections inside a method to improve readability 8.2 Blank Spaces Blank spaces should be used in the following circumstances: A keyword followed by a parenthesis should be separated by a space. Example: while (true) { ... } Note that a blank space should not be used between a method name and its opening parenthesis. This helps to distinguish keywords from method calls. A blank space should appear after commas in argument lists. All binary operators except . should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment (“++”), and decrement (“--”) from their operands. Example: a += c + d; a = (a + b) / (c * d); while (d++ = s++) { n++; } prints("size is " + foo + "\n"); The expressions in a for statement should be separated by blank spaces. Example: for (expr1; expr2; expr3) Casts should be followed by a blank space. Examples: myMethod((byte) aNum, (Object) x); myMethod((int) (cp + 5), ((int) (i + 3)) + 1); 9 - Naming Conventions Naming conventions make programs more understandable by making them easier to read. 140 141 They can also give information about the function of the identifier—for example, whether it’s a constant, package, or class—which can be helpful in understanding the code. Identifier Type Packages Classes Interfaces Methods Rules for Naming Examples The prefix of a unique package name is always written in all-lowercase ASCII letters and should be one of the toplevel domain names, currently com, edu, gov, mil, net, org, com.sun.eng com.apple.quicktime.v2 edu.cmu.cs.bovik.cheese class Raster; 141 142 Identifier Type Constants Rules for Naming Except for variables, all instance, class, and class constants are in mixed case with a lowercase first letter. Internal words start with capital letters. Variable names should not start with underscore _ or dollar sign $ characters, even though both are allowed. Variable names should be short yet meaningful. The choice of a variable name should be mnemonic— that is, designed to indicate to the casual observer the intent of its use. Onecharacter variable names should be avoided except for temporary “throwaway” variables. Common names for temporary variables are i, j, k, m, and n for integers; c, d, and e for characters. The names of variables declared class constants Variables The names of variables declared class constants and of ANSI constants should be all uppercase with words separated by underscores (“_”). (ANSI constants should be avoided, for ease of debugging.) int i; char c; float myWidth; static final int MIN_WIDTH = 4; static final int MAX_WIDTH = 999; static final int GET_THE_CPU = 1; 142 143 10 - Programming Practices 10.1 Providing Access to Instance and Class Variables Don’t make any instance or class variable public without good reason. Often, instance variables don’t need to be explicitly set or gotten—often that happens as a side effect of method calls. One example of appropriate public instance variables is the case where the class is essentially a data structure, with no behavior. In other words, if you would have used a struct instead of a class (if Java supported struct), then it’s appropriate to make the class’s instance variables public. 10.2 Referring to Class Variables and Methods Avoid using an object to access a class (static) variable or method. Use a class name instead. For example: classMethod(); //OK AClass.classMethod(); //OK anObject.classMethod(); //AVOID! 10.3 Constants Numerical constants (literals) should not be coded directly, except for -1, 0, and 1, which can appear in a for loop as counter values. 10.4 Variable Assignments Avoid assigning several variables to the same value in a single statement. It is hard to read. Example: fooBar.fChar = barFoo.lchar = 'c'; // AVOID! Do not use the assignment operator in a place where it can be easily confused with the equality operator. Example: if (c++ = d++) { // AVOID! (Java disallows) ... } should be written as if ((c++ = d++) != 0) { ... } Do not use embedded assignments in an attempt to improve run-time performance. This is the job of the compiler. Example: d = (a = b + c) + r; // AVOID! should be written as a = b + c; d = a + r; 10.5 Miscellaneous Practices 10.5.1 Parentheses It is generally a good idea to use parentheses liberally in expressions involving mixed operators to avoid operator precedence problems. Even if the operator precedence seems clear to you, it might not be to others—you shouldn’t assume that other programmers know precedence as well as you do. 143 144 if (a == b && c == d) // AVOID! if ((a == b) && (c == d)) // USE 10.5.2 Returning Values Try to make the structure of your program match the intent. Example: if ( booleanExpression) { return true; } else { return false; } should instead be written as return booleanExpression; Similarly, if (condition) { return x; } return y; should be written as return (condition ? x : y); 10.5.3 Expressions before ‘?’ in the Conditional Operator If an expression containing a binary operator appears before the ? in the ternary ?: operator, it should be parenthesized. Example: (x >= 0) ? x : -x; 10.5.4 Special Comments Use XXX in a comment to flag something that is bogus but works. Use FIXME to flag something that is bogus and broken. 11 - Code Examples 11.1 Java Source File Example The following example shows how to format a Java source file containing a single public class. Interfaces are formatted similarly. For more information, see “Class and Interface Declarations” on page 3 and “Documentation Comments” on page 8 144 145 19 /* * @(#)Blah.java 1.82 99/03/18 * * Copyright (c) 1994-1999 Sun Microsystems, Inc. * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. * All Rights Reserved. * * This software is the confidential and proprietary information of Sun * Microsystems, Inc. ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into * with Sun. */ package java.blah; import java.blah.blahdy.BlahBlah; /** * Class description goes here. * * @version 1.82 18 Mar 1999 * @author Firstname Lastname */ public class Blah extends SomeClass { /* A class implementation comment can go here. */ /** classVar1 documentation comment */ public static int classVar1; /** * classVar2 documentation comment that happens to be * more than one line long */ private static Object classVar2; /** instanceVar1 documentation comment */ public Object instanceVar1; /** instanceVar2 documentation comment */ protected int instanceVar2; /** instanceVar3 documentation comment */ private Object[] instanceVar3; /** * ... constructor Blah documentation comment... */ public Blah() { // ...implementation goes here... } /** * ... method doSomething documentation comment... */ public void doSomething() { // ...implementation goes here... } /** * ...method doSomethingElse documentation comment... * @param someParam description */ public void doSomethingElse(Object someParam) { // ...implementation goes here... } } 145 146 2.3.4.3.3 Round Trip Engineering 2.3.4.3.3.1 Beispiel Bank // Einfügen 2.3.4.3.3.2 Beispiel Wertpapier public class A extends WP { public A() { } } public class D { public IWP theIWP; public K theK; public D() { } } public class FV extends WP { public K theK; public FV() { } } public interface IWP { } public class K { protected String n; public D theD; public FV theFV; 146 147 public K() { } /** @roseuid 3CCA79B6001F */ public String getN() { } } public class O extends WP { public O() { } } public abstract class WP implements IWP { public WP() { } } // Einfügen 147 148 2.3.4.3.3.3 Sinn und Zweck Die Umsetzung der Design Klassen in Code kann partiell durch Codegenerierung erfolgen. Im Rahmen der Codegenerierung werden z.B. Associations in eine Referenz auf die entsprechende Klasse umgesetzt. Der Name der Referenz entspricht der Rolle der Klasse. Die Anzahl der Pointer entspricht der Muliplizität der Association. Die Implementierung der Operationen erfolgt durch Umsetzung der Interaction Diagrams, der Statechart Diagrams und des Pseudocodes aus dem Design Model. Dies geschieht in der Regel manuell. Das Reeengineering dient dazu, im Code vorgenommene Änderungen wieder in die UML Diagramme zu integrieren. Somit befinden sich die Diagramme und der Code in einem einheitlichen Zustand, ohne die Änderungen am Code per Hand in die UML Notation übertragen zu müssen. Die Kombination aus Codegenerierung und Reengineering bezeichnet man als Round Trip Engineering. 2.3.4.3.3.4 2.3.4.3.3.4.1 Tutorial für Round Trip Engineering mit Rational Rose Grundeinstellungen 2.3.4.3.3.4.1.1 Setzen des CLASSPATH Grundvoraussetzung ist ein korrekt installiertes Java SDK (hier das aktuelle 1.4) und somit eine komplett gesetzte CLASSPATH Variable. Die CLASSPATH Variable wird z.B. vom Java Compiler verwendet, um die SystemBibliotheken (oder die eigenen) zu finden. Unter Windows NT/2000 setzt man folgendermaßen: Programme Einstellungen Systemsteuerung System Sytemeigenschaften Erweitert Umgebungsvariablen Neu Bei „Name der Variablen“: CLASSPATH eingeben Bei „Wert der Variablen“ werden die Pfade zu den einzelnen JAR Dateien und notwendigen Verzeichnissen eingegeben. So sei das SDK unter E:\jdk1.4.0 installiert. Somit wird folgendes eingetragen: E:\jk1.4.0\jre\lib\rt.jar;E:\jdk1.4.0\jre\lib\jce.jar;E:\jdk1.4.0\jre\lib\jsse.jar;E:\jdk1.4.0\jre\lib\jaw s.jar;E:\jdk1.4.0\jre\lib\charsets.jar;E:\jdk1.4.0\jre\lib\sunrsaign.jar\;E:\jdk1.4.0\jre\lib\ext\dnsn s.jar;E:\jdk1.4.0\jre\lib\ext\ldapsec.jar;E:\jdk1.4.0\jre\lib\ext\localedata.jar;E:\jdk1.4.0\lib\dt.jar ;E:\jdk1.4.0\lib\htmlconverter.jar;E:\jdk1.4.0\lib\tools.jar;. 2.3.4.3.3.4.1.2 Anlegen eines Projektverzeichnisses Z.B. auf Laufwerk F: das Verzeichnis JavaTest. 2.3.4.3.3.4.1.3 Starten von Rose Die Einstellungen beziehen sich auf die Rational Rose 2000 enterprise Version. Somit können sich die Menüpunkte je nach Version unterscheiden, wobei die Vorgehensweise 148 149 identisch ist. Nach dem Starten wird beim Wizard „Create New Model“ das WorkModel „J2SE 1.3“ oder ein höher ausgewählt. Dies ist notwendig, damit Rose alle benötigten Java Klassen und Elemente auflösen kann. 2.3.4.3.3.4.1.4 Hinzufügen des Projektverzeichnisses zum CLASSPATH Nun wird das Projektverzeichnis den Java Projekt Spezifikationen hinzugefügt: Tools Java Project Specification Icon: New Directory Unser Verzeichnis auf Laufwerk F auswählen und mit OK bestätigen Mit OK das Verzeichnis übernehmen 2.3.4.3.3.4.1.5 Speichern des Rose Projektes Nun wird das Rose Projekt gespeichert. Hier: in unserem Projektverzeichnis unter dem Namen HelloWorld.mdl. 2.3.4.3.3.4.2 Vorgehensweise am Beispiel HelloWorld 2.3.4.3.3.4.2.1 LogicalView: Erzeugen der Start Klasse Das HelloWorld Projekt soll aus 2 Klassen bestehen. Die erste ist nur die Startklasse mit der Methode „main“, während die andere Klasse „HelloWorldGUI“ das eigentliche Programm bildet. In dieser Klasse soll eine JFrame erzeugt und die Meldung „Hello World“ ausgegeben werden. Hieraus folgt folgendes UML Diagramm: 2.3.4.3.3.4.2.2 Erzeugen des Codes der Klasse Markieren der Klasse „HelloWorld“ Tools Java Generate Java Nun erscheint folgender Dialog 149 150 In diesem wird unsere Klasse einem Verzeichnis (welches vorher in der Classpath Variable aufgenommen wurde) zugewiesen. Dies muss für jede Klasse nur 1 mal absolviert werden. Select Java Files oder SelectAll Select Class Path Entry Nach dem Bestätigen des „Assign“ Buttons und des OK Buttons wird der Code erzeugt. 2.3.4.3.3.4.2.3 Bearbeiten des Codes Die Klasse HelloWorld.java wird nun in einem Editor (oder IDE) geladen und sieht folgendermaßen aus: //Source file: F:\\JAVATEST\\HelloWorld.java public class HelloWorld { } Nun wir den restlichen Code (hier blau): //Source file: F:\\JAVATEST\\HelloWorld.java public class HelloWorld { public static void main(String args[]) { HelloWorldGUI hwg = new HelloWorldGUI(); Hwg.setSize(300,100); hwg.setLocation(100,100); hwg.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { System.exit(0); } }); hwg.setVisible(true); } } Somit ergibt sich die nächste Klasse aus dem Code: HelloWorldGUI. Diese wird nun ebenfalls erst in UML verfasst und dann ebenfalls mittels Codeerzeugung generiert. Vorher ist es jedoch sinnvoll das UML Diagramm der HelloWorld Klasse auf den neuesten (den die Datei HelloWorld.java vorgibt) Stand zu bringen. 2.3.4.3.3.4.2.4 ReverseEngineering der HelloWorld Klasse Tools Java 150 151 Reverse Engineer Java: Auswahl des Verzeichnisses im Verzeichnisbaum Mittels „Add“ oder „AddAll“ bei mehreren Dateien die Klasse HelloWorld.java aufnehmen Per „SelectAll“ oder per Click auf den Namen die upzudatende Klasse auswählen. Per „Reverse“ starten Wenn keine Fehler gemeldet wurden einfach Fenster per „Done“ schließen Ergebnis: Die Datei HelloWorld.java darf nicht gelöscht werden, da sonst deren Zustand nicht komplett per Codeerzeugung hergestellt werden kann. D.h. Das UML Diagramm und die Datei bilden jetzt eine Einheit und sind nicht trennbar. 2.3.4.3.3.4.2.5 Erzeugen von HelloWorldGUI Das UML Diagramm wird erweitert : 151 152 2.3.4.3.3.4.2.6 Erzeugen des Codes der Klasse Der Code wird wie unter 2.3.4.3.3.4.2.2 beschreiben erzeugt 2.3.4.3.3.4.2.7 Bearbeiten des Codes Der Code sieht folgendermaßen aus //Source file: F:\\JAVATEST\\HelloWorldGUI.java import javax.swing.*; import java.awt.*; public class HelloWorldGUI extends JFrame { public HelloWorldGUI() { setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); } public void paint(Graphics g) { g.drawString(„HelloWorld“,100,50); } } 2.3.4.3.3.4.2.8 ReverseEngineering der HelloWorldGUI Klasse Durchführung wie oben beschrieben 152 153 2.3.4.4 Integrieren des Systems Der Integrator integriert das System in Übereinstimmung mit dem Build Plan. 2.3.5 Test 2.3.5.1 Beispiel Klassentest MyMath Kleine Testumgebung für den Klassentest einer selbst geschriebenen MyMath Klasse. 2.3.5.1.1 BlackBoxTest 2.3.5.1.1.1 Testling Interface public interface IMiniMath { public double calcDurch (double a, double b); public double calcPotenz (double b, int n); public double calcMal (double a, double b); public double calcExp (double x, double epsilon); public int getError(); public void resetError(); } 2.3.5.1.1.2 Testapplikation public class TestApp extends JFrame implements ActionListener { private TestController testController = null; private Tracer tracer = null; public TestApp() { super("Test Applikation"); addWindowListener(new WindowClosingAdapter()); JMenuBar menubar = new JMenuBar(); menubar.add(createFileMenu()); setJMenuBar(menubar); testController = new TestController(); tracer = new Tracer(); } public void actionPerformed(ActionEvent event) { String c = event.getActionCommand(); System.out.println(c); if (c.equals("Start")) { System.out.println("Start Test"); testController.start(); } if (c.equals("Stop")) { System.out.println("Stop Test"); testController.stop(); } if (c.equals("Beenden")) { System.out.println("Beenden"); tracer.shutDown(); } } //---Private Methoden--------------private JMenu createFileMenu() { JMenu ret = new JMenu("Test"); ret.setMnemonic('T'); JMenuItem mi; 153 154 //Starten mi = new JMenuItem("Start", 'S'); setCtrlAccelerator(mi, 'S'); mi.addActionListener(this); ret.add(mi); //Separator ret.addSeparator(); //Speichern mi = new JMenuItem("Stop", 'p'); setCtrlAccelerator(mi, 'S'); mi.addActionListener(this); ret.add(mi); //Separator ret.addSeparator(); //Beenden mi = new JMenuItem("Beenden", 'B'); setCtrlAccelerator(mi, 'B'); mi.addActionListener(this); ret.add(mi); return ret; } private void setCtrlAccelerator(JMenuItem mi, char acc) { KeyStroke ks = KeyStroke.getKeyStroke( acc, Event.CTRL_MASK ); mi.setAccelerator(ks); } public static void main(String[] args) { TestApp frame = new TestApp(); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } 2.3.5.1.1.3 Tracer public class Tracer { static Tracer tracer = null; private Calendar calendar = null; private PrintWriter out = null; public Tracer () { tracer = this; calendar = Calendar.getInstance( TimeZone.getTimeZone( "ECT" )); try { out = new PrintWriter( new FileWriter( "TracerLog.txt" )); } catch (IOException ioe) { } } public void shutDown() { out.close(); String s = getTime() + " Monitor File closed !"; System.out.println(s); out.println(s); } static public void put(String s) { tracer.send(s); } public void send(String s) { String str = getTime() + " Traceeintrag : " + s; System.out.println(str); out.println(str); } 154 155 private String getTime() { calendar.setTime( new Date()); return setLeadingZero( calendar.get( Calendar.HOUR_OF_DAY )) + ":" + setLeadingZero( calendar.get( Calendar.MINUTE )) + ":" + setLeadingZero( calendar.get( Calendar.SECOND )); } private String setLeadingZero( int i ) { return ( i < 10 ) ? "0" + String.valueOf( i ): String.valueOf( i ); } } 2.3.5.1.1.4 TestController public class TestController { static public boolean whiteBoxTest = false; public TestController () { } public void start() { Tracer.put("Start des Testlaufs !!!"); // Test der Klasse MiniMath IMiniMath mm = new MiniMath(); double result; /* // BlackBoxTest // Testserie für die Funktion calcDurch() // Testfall Standarddivision mm.resetError(); result = mm.calcDurch(3.5, 0.5); Tracer.put("Result " + result); Tracer.put("error " + mm.error); Tracer.put(" Testfall Ende"); // Testfall Division durch negative Zahl mm.resetError(); result = mm.calcDurch(0.8, -0.2); Tracer.put("Result " + result); Tracer.put("error " + mm.error); Tracer.put(" Testfall Ende"); // Testfall Division durch Null mm.resetError(); result = mm.calcDurch(3, 0); Tracer.put("Result " + result); Tracer.put("error " + mm.error); Tracer.put(" Testfall Ende"); */ /* // BlackBoxTest // Testserie für die Funktion calcPotenz() // Testfall Standard mm.resetError(); result = mm.calcPotenz(2, 3); Tracer.put("Result " + result); Tracer.put("error " + mm.error); Tracer.put(" Testfall Ende"); // Testfall Negativer Exponent mm.resetError(); result = mm.calcPotenz(2, -3); Tracer.put("Result " + result); Tracer.put("error " + mm.error); Tracer.put(" Testfall Ende"); // Testfall Exponent 0 mm.resetError(); result = mm.calcPotenz(2, 0); Tracer.put("Result " + result); Tracer.put("error " + mm.error); Tracer.put(" Testfall Ende"); */ 155 156 // WhiteBoxTest whiteBoxTest = true; // Testserie für die Funktion calcPotenz() // Testfall Standard mm.resetError(); result = mm.calcPotenz(2, 3); Tracer.put("Result " + result); Tracer.put("error " + mm.getError()); Tracer.put(" Testfall Ende"); // Testfall Negativer Exponent mm.resetError(); result = mm.calcPotenz(2, -3); Tracer.put("Result " + result); Tracer.put("error " + mm.getError()); Tracer.put(" Testfall Ende"); // Testfall Exponent 0 mm.resetError(); result = mm.calcPotenz(2, 0); Tracer.put("Result " + result); Tracer.put("error " + mm.getError()); Tracer.put(" Testfall Ende"); // End of WhiteBoxTest whiteBoxTest = false; /* // BlackBoxTest // Testserie für die Funktion calcExp() // Testfall exp(1) result = mm.calcExp(1, 0.001); Tracer.put("Result " + result); Tracer.put(" Testfall Ende"); */ } public void stop() { Tracer.put("Beenden des Testlaufs !!!"); } } 2.3.5.1.1.5 Testprozedur Doppelclick auf TestApp im Verzeichnis .../TestSystem Aktivieren des Codes für den Testfall durch Entfernen der Kommentare Ausführen der TestApp Applikation Click auf MenueItem Start Öffnen der Datei ExpectedTracerLog.txt im Verzeichnis .../TestSystem Vergleich der erwarteten Ausgaben mit den Ausgaben der Datei ExpectedTracerLog.txt im Verzeichnis .../TestSystem 2.3.5.1.1.6 Einige Testfälle 2.3.5.1.1.6.1 Test calcDurch() 2.3.5.1.1.6.1.1 Testfall Standarddivision 2.3.5.1.1.6.1.1.1 Voraussetzungen error = 0 2.3.5.1.1.6.1.1.2 Eingabe a = 3.5 b = 0.5 156 157 2.3.5.1.1.6.1.1.3 Erwartete Ausgabe 7 error = 0 2.3.5.1.1.6.1.1.4 Ausgabe 7 error = 0 2.3.5.1.1.6.1.2 Testfall Division durch negative Zahl 2.3.5.1.1.6.1.2.1 Voraussetzungen error = 0 2.3.5.1.1.6.1.2.2 Eingabe a = 0.8 b = -0.2 2.3.5.1.1.6.1.2.3 Erwartete Ausgabe -4 error = 0 2.3.5.1.1.6.1.2.4 Ausgabe -4 error = 0 2.3.5.1.1.6.1.3 Testfall Division durch Null 2.3.5.1.1.6.1.3.1 Voraussetzungen error = 0 2.3.5.1.1.6.1.3.2 Eingabe a=3 b=0 2.3.5.1.1.6.1.3.3 Erwartete Ausgabe 0 error = -1 2.3.5.1.1.6.1.3.4 Ausgabe 157 158 Infinity error = 0 2.3.5.1.1.6.2 Test calcPotenz() 2.3.5.1.1.6.2.1 Testfall Standard 2.3.5.1.1.6.2.1.1 Voraussetzungen error = 0 2.3.5.1.1.6.2.1.2 Eingabe b=2 n=3 2.3.5.1.1.6.2.1.3 Erwartete Ausgabe 8 error = 0 2.3.5.1.1.6.2.1.4 Ausgabe 8 error = 0 2.3.5.1.1.6.2.2 Testfall Negativer Exponent 2.3.5.1.1.6.2.2.1 Voraussetzungen error = 0 2.3.5.1.1.6.2.2.2 Eingabe b=2 n = -3 2.3.5.1.1.6.2.2.3 Erwartete Ausgabe 0.125 error = 0 2.3.5.1.1.6.2.2.4 Ausgabe 0.5 error = 0 2.3.5.1.1.6.2.3 Testfall Exponent Null 158 159 2.3.5.1.1.6.2.3.1 Voraussetzungen error = 0 2.3.5.1.1.6.2.3.2 Eingabe b=3 n=0 2.3.5.1.1.6.2.3.3 Erwartete Ausgabe 1 error = 0 2.3.5.1.1.6.2.3.4 Ausgabe 2 error = 0 2.3.5.1.2 WhiteBoxTest 2.3.5.1.2.1 Testling public class MiniMath implements IMiniMath { static int whileCounter_Block_2_3_while = 0; private int error = 0; public double calcDurch (double a, double b) { if (0 != a) { return a/b; } else { error = -1; return 0; } } public double calcPotenz (double b, int n) { int i; double p = 0; if (0 > b) { if (TestController.whiteBoxTest) Tracer.put("Block_1_if_then"); error = -1; } else { if (TestController.whiteBoxTest) Tracer.put("Block_2_if_else"); if (0 == n) { if (TestController.whiteBoxTest) Tracer.put("Block_2.1_if_then"); p = 1; } { 159 160 if (TestController.whiteBoxTest) Tracer.put("Block_2.2_if_else"); } p = b; i = 1; while (i < n) { whileCounter_Block_2_3_while++; if (TestController.whiteBoxTest) Tracer.put("Block_2.3_while Loops " + whileCounter_Block_2_3_while); p = p * b; i++; } if (0 > n) { if (TestController.whiteBoxTest) Tracer.put("Block_2.4_if_then"); p = 1/p; } else { if (TestController.whiteBoxTest) Tracer.put("Block_2.5_if_else"); } } return p; } public double calcMal (double a, double b) { return a*b; } public double calcExp (double x, double epsilon) { double summand = 1; int k; double expo; if (x == 0) { expo = 0; } else { expo = 1; k = 0; do { summand = summand * x / (k + 1); expo = expo + summand; k = k+1; }while (summand >= epsilon); } System.out.println("Math Funktion exp(x) = " + java.lang.Math.exp(x)); return expo; } public int getError() { return error; } public void resetError() { error = 0; } } 2.3.5.1.2.2 Test Log 160 161 TracerLog.txt 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 12:25:36 Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag Traceeintrag : : : : : : : : : : : : : : : : : : : : : : Start des Testlaufs !!! Block_2_if_else Block_2.2_if_else Block_2.3_while Loops 1 Block_2.3_while Loops 2 Block_2.5_if_else Result 8.0 error 0 Testfall Ende Block_2_if_else Block_2.2_if_else Block_2.4_if_then Result 0.5 error 0 Testfall Ende Block_2_if_else Block_2.1_if_then Block_2.2_if_else Block_2.5_if_else Result 2.0 error 0 Testfall Ende 161 162 2.3.5.1.2.3 Kontrollflussgraph 162 163 2.3.5.2 Beispiel für Test Guidelines Guidlines wurden für industrielle Bildverarbeitungs-Projekte mit einer Größe von ca. 5 Entwickler, 500 Klassen geschrieben. 2.3.5.2.1 Klassentest 2.3.5.2.1.1 Ziel Der Klassentest überprüft das Zusammenspiel der einzelnen Operationen einer Klasse und das Zusammenspiel einer Klasse mit ihren Oberklassen. Das Zusammenspiel mit Objekten anderer verwendeter Klassen wird dabei nicht explizit überprüft. Verwendet die zu testende Klasse andere nicht implementierte Klassen, so werden diese durch Stubs ersetzt. 2.3.5.2.1.2 Zu testende Klassen Alle wichtigen Klassen sind zu testen. Existiert eine Vererbungshierarchie, so sind jeweils die letzten abgeleiteten Klassen zu testen. Ein isolierter Test von Schnittstellen oder abstrakten Basisklassen kann entfallen. 2.3.5.2.1.3 2.3.5.2.1.3.1 Black Box Test (Funktionaler Test) Ziel Der Specification Test oder Black Box Test verifiziert das externe Verhalten der Klasse. Specification testing is done to verify the components behaviour without considering how the behaviour is implemented within the component. The specification tests thus look at what output the component will return, when given certain input and when starting in a particular state. 2.3.5.2.1.3.2 Definition der Testfälle 2.3.5.2.1.3.2.1 Allgemeine Hinweise Beim Black Box Test werden die Testfälle aus der Spezifikation der Klasse abgeleitet. Ein vollständiger Funktionstest ist i.A. nicht möglich. Die Testfälle sind daher so auszuwählen, dass die Wahrscheinlichkeit groß ist, Fehler zu finden. Alle wichtigen, kritischen und mit hohem Risiko behafteten Funktionen der Klasse sind zu testen. Die funktionale Überdeckung soll mindestens 70% betragen. Jede Unterklasse definiert einen neuen Kontext, der zu einem fehlerhaften Verhalten von geerbten Operationen führen kann. Polymorphe Methodenaufrufe sind zu testen. Überladene Operatoren und Methoden sind zu testen. 2.3.5.2.1.3.2.2 Äquivalenzklassenbildung 163 164 Zum Argumentbereich einer Operation gehören nicht nur ihre Eingangsparameter sondern auch der Zustand ihres Objektes. Die Kombinationsmöglichkeiten für Input, Output und States ermöglichen in der Regel keine vollständige Testüberdeckung. Daher sind Input, Output und States in Äquivalenzklassen zu zerlegen und alle wichtigen Kombinationen von Äquivalenzklassen zu testen. Es wird davon ausgegangen, dass ein Programm bei der Verarbeitung eines Repräsentanten aus einer Äquivalenzklasse so reagiert, wie bei allen anderen Werten dieser Äquivalenzklasse. Wenn das Programm mit dem repräsentativen Wert fehlerfrei läuft, dann ist zu erwarten, dass es auch für andere Werte aus dieser Äquivalenzklasse korrekt funktioniert. Beispiel 1 <= monat <= 12 A1: 1 <= monat <= 12 A2: 12 < monat A3: monat < 1 Testfälle: monat = 5 monat = 17 monat = -3 Die Ausgabeäquivalenzklassen müssen in entsprechende Äquivalenzklassen der Eingabewerte umgeformt werden. Die Testfälle für ungültige Äquivalenzklassen werden durch Auswahl eines Testdatums aus einer ungültigen Äquivalenzklasse gebildet. Dieses Testdatum wird mit Werten kombiniert, die ausschließlich aus gültigen Äquivalenzklassen entnommen sind. Werden mehrere fehlerhafte Eingaben pro Testfall verwendet, so ist nicht tranparent, welches fehlerhafte Datum die Fehlerbehandlung ausgelöst hat. 2.3.5.2.1.3.2.3 Grenzwertanalyse Erfahrungen haben gezeigt, dass Testfälle, die die Grenzwerte der Äquivalenzklassen abdecken oder in der unmittelbaren Umgebung dieser Grenzen liegen, besonders häufig Fehler aufdecken. Daher soll nicht irgendein Element aus der Äquivalenzklasse als Repräsentant ausgewählt werden, sondern mehrere Elemete sollen so ausgesucht werden, dass der Rand der Äquivalenzklasse getestet wird. Beispiel 1 <= monat <= 12 A1 : 1 <= monat <= 12 A2 : 12 < monat A3 : monat < 1 Testfälle monat = 0 monat = 1 monat = 12 monat = 13 Weitere Beispiele für Grenzwerte Leeres Array, volles Array, Array mit nur einem freien oder nur einem gefüllten Platz 164 165 2.3.5.2.1.3.2.4 Test spezieller Werte (special values testing) Aus der Erfahrung heraus soll eine Liste möglicher Fehlersituationen aufgestellt und daraus Testfälle abgeleitet werden. Beispiele für spezielle Werte : Null-Werte Sonderzeichen und Steuerzeichen 2.3.5.2.1.3.2.5 Zufallstest Aus dem Wertebereich der Eingabedaten werden zufällig Testfälle erzeugt. Tester neigen dazu, bestimmte Testfälle zu erzeugen, die auch bei der Implementierung als naheliegend betrachtet worden sind und für die sich das Programm folglich gutartig verhält. 2.3.5.2.1.3.2.6 Ableitung von Testfällen aus State Charts Die Operationen einer Klasse sind über die Variablen der Klasse gekoppelt. Für Klassen mit relevanten Zuständen sollen die Testfälle so spezifiziert werden, dass alle wichtigen und kritischen, d.h. mit hohem Risiko behafteten, Folgen von Zustandsübergängen mit den Testfällen abgedeckt werden. Ein Objekt muss durch explizite Initialisierung (Konstruktor, Set-Funktionen) oder durch vorhergehende Testfälle in einen für den aktuellen Testfall relevanten Zustand versetzt werden. 2.3.5.2.1.4 2.3.5.2.1.4.1 White Box Test (Strukturtest, Glass Box Test) Ziel Die Spezifikation einer Klasse besitzt ein höheres Abstraktionsniveau als die Implementierung. Daher soll der Klassentest nicht auf Black Box Tests beschränkt werden. Der Structure Test oder White Box Test verifiziert das interne Verhalten der Klasse. RUP Text : Structure testing is done to verify that a component works internally as intended. The tester should be sure to test the most commonly followed and the most critical paths through the algorithms and paths that are associated with high risks. 2.3.5.2.1.4.2 Definition der Testfälle beim Kontrollflussorientierte Strukturtestverfahren Bein kontrollflussorientierten Test werden Strukturelemente wie Anweisungen oder Verzweigungen verwendet, um Testziele zu definieren. Jeder Knoten eines Kontrollflussgraphen stellt eine ausführbare Anweisung dar. Eine gerichtete Kante von einem Knoten zu einem anderen beschreibt einen möglichen Kontrollfluss. Die Kanten werden als Zweige bezeichnet. 2.3.5.2.1.4.2.1 Anweisungsüberdeckung Der Anweisungsüberdeckungs- oder C0 –Test verlangt die Ausführung aller Knoten des Kontrollflussgraphen. (C = Coverage) 165 166 Metrik C0 = Anz ausgef Anw / Anz vorh Anw Probleme : Kontrollstrukturen werden nicht berücksichtigt. Fehleridentifizierungsquote erfahrungsgemäß ca 18% 2.3.5.2.1.4.2.2 Zweigüberdeckung Der Zweigüberdeckungs- oder C1 –Test fordert verschärfend die Ausführung aller Kanten. Die Anweisungsüberdeckung ist in der Zweigüberdeckung vollständig enthalten. C1 = Anz ausgef Zweige / Anz vorh Zweige Probleme : Kombinationen von komplexen Bedingungen werden nicht berücksichtigt. Schleifen werden nicht ausreichend gestestet. Abhängigkeiten zwischen Verzweigungen werden nicht berücksichtigt. Fehleridentifizierungsquote erfahrungsgemäß 34% 2.3.5.2.1.4.2.3 Pfadüberdeckung Der vollständige Pfadüberdeckungstest fordert die Ausführung aller unterschiedlichen Pfade des zu testenden Programmes. Ein Pfad ist eine Sequenz von Knoten des Kontrollflussgraphen. Die Anzahl der möglichen Pfade ist bereits bei kleinen überschaubaren Kontrollstrukturen unpraktikabel hoch. Ein Teil der konstruierbaren Pfade ist nicht ausführbar, da sich die Bedingungen an den Verzweigungen gegenseitig ausschließen. Daher sollen nur die wichtigsten Pfade durch den Code durchlaufen werden. Dies beinhaltet insbesondere die am häufigsten durchlaufenen, kritischen und mit hohem Risiko verbundenen Pfade. The tester should be sure to test the most commonly followed and the most critical paths through the algorithms and paths that are associated with high risks. (Boundary Interior Pfadtest : Beim Testen von Schleifen wird auf die Überprüfung von Pfaden verzichtet, die durch mehr als einmalige Schleifenwiederholungen erzeugt werden.) 2.3.5.2.1.4.2.4 Bedingungsüberdeckung Es ist darauf zu achten, dass alle wichtigen Bedingungen im Code mit den Testfällen abgedeckt werden. Minimale Mehrfach-Bedingungsüberdeckung Jede Bedingung -ob atomar oder nicht- muss mindestens einmal true und einmal false sein. Alternative Kriterien Einfache Bedingungsüberdeckung 166 167 Die Evaluation aller atomaren Bedingungen muss mindestens einmal den Wahrheitswert true oder false ergeben. 2.3.5.2.2 Integrationstest 2.3.5.2.2.1 Ziel Integrationstestfälle verifizieren, dass eine bestimmte Menge von Komponenten nach der Integration richtig zusammenarbeiten. Integration test cases are used to verify that the components interact properly with each other after they have been integrated. 2.3.5.2.2.2 Für welche Gruppen von Klassen ist ein Integrationstest durchzuführen Die inkrementelle Entwicklung führt zu einer inkrementellen Integrationsstrategie. Die Zusammenstellung der zu integrierenden Klassen orientiert sich dabei an den zu realisierenden Use Cases. Für jeden Use Case ist die Interaktion aller wichtigen an der Realiserung beteiligten Klassen zu testen. Das Zusammenspiel bereits integrierter Teilsysteme wird dabei nicht erneut aufgezeichnet und überprüft. 2.3.5.2.2.3 Definition der Testfälle Die Testfälle ergeben sich aus den Interaktionsdiagrammen der Use Case Realisierungen. Integration test cases can be derived from use case realizations. The tester should try to find a set of test cases, each of with tests an interesting scenario through a use case realization. Each use case will be tested for its normal flow and at least one alternative flow. Insbesondere sollen Use Cases getestet werden, die parallel ablaufen oder ausgeführt werden, sich gegenseitig beeinflussen, in die mehrere Threads oder Prozesse involviert sind. 2.3.5.2.3 Regressionstest Wird in dem Testling ein Fehler korrigiert, so ist mit den bereits vorhandenen Testfällen ein Regressionstest durchzuführen. Es soll darauf geachtet werden, dass möglichst viele Testfälle im Verlaufe der Iterationen wiederverwendet werden können. 2.3.5.2.4 Systemtest 2.3.5.2.4.1 Ziel Der Systemtest ist der abschließende Test in der realen Umgebung ohne den Auftraggeber. System tests are used to test that the system functions properly as a whole. Der Test basiert auf den für das System spezifizierten Use Cases. Das Innere des Systems ist nicht sichtbar. 2.3.5.2.4.2 Definition der Testfälle Funktionstest (Vollständigkeit) 167 168 Das System muss alle funktionalen Anforderungen erfüllen. Massentest (Datenvolumen) Das System sollte mit umfangreichen Datenmengen getestet werden. Zeittest Bei starker Systembelastung ist zu prüfen, ob die geforderten Antwortzeiten und Durchsatzraten eingehalten werden. Lasttest (Zuverlässigkeit) Das System ist über längere Zeit unter Spitzenbelastung zu testen. Z.B. Mehrbenutzerbetrieb mit der maximalen Benutzeranzahl. Stresstest (Fehlertoleranz, Robustheit) Das System wird unter Überlast getestet. Z.B. wird der Arbeitsspeicher reduziert. Usability Test (Benutzbarkeit) Es wird die Verständlichkeit, Erlernbarkeit und Bedienbarkeit aus der Sicht des Endbenutzers getestet. Sicherheitstest Hier werden Datenschutzmechanismen getestet. Z.B. verschlüsselte Passwörter, Überprüfung von Zugriffsrechten. Interoperabilitätstest Es wird die Zusammenarbeit mit anderen Systemen getestet. Z.B. auch Ausfall von Hard- und Softwarekomponenten, mit denen das System kommuniziert. Konfigurationstest Das System wird auf unterschiedlichen Plattformen getestet. Dokumententest Vorhandensein und Güte der Benutzer- und Wartungsdokumentation ist zu testen. 2.3.5.2.5 Abnahmetest (Acceptance Test) Systemtest unter Beobachtung, Mitwirkung und Federführung des Auftraggebers unter realen Einsatzbedingungen beim Auftraggeber und mit Daten des Auftraggebers. Beim Alpha-Test wird das System in der Zielumgebung des Herstellers durch Anwender erprobt. Beim Beta-Test wird das System bei ausgewählten Pilotkunden in deren eigener Umgebung zur Probenutzung zur Verfügung gestellt. Verification Es wird geprüft, ob das System entsprechend der Spezifikation entwickelt wurde. Validation Es wird geprüft, ob das System mit den Erwartungen des Kunden übereinstimmt. 2.3.5.2.6 Testdurchführung 2.3.5.2.6.1 Testumgebung Die Testungebung besteht aus einem Testcontroller, dem Tracer, den Stubs und einer Standardtestapplikation (Testrahmen). 168 169 Der Tracer protokolliert die Botschaften an Objekte, die Input und Outputparameter von Operationen und die States des Testlings. Stubs werden benötigt, wenn eine zu testende Systemkomponente andere Systemkomponenten aufruft, die zwar spezifiziert aber nicht implementiert oder aus anderen Gründen (z.B. Hardware) nicht verwendet werden sollen. Stubs simulieren die verlangten Funktionen mit eingeschränktem Leistungsumfang. Ein Testcontroller wird benötigt, um eine Systemkomponente zu testen, deren Dienst nicht direkt von der Benutzungsoberfläche aufgerufen werden kann oder soll. Der Testcontroller erzeugt das Testobjekt, versetzt es in den gewünschten Ausgangszustand und stößt die gewünschte Operation an. Der Testcontroller erzeugt ggf. Stubs. Im Falle komplexer Testszenarien wird derTestcontroller ggf. durch TestHandler Hilfsklassen ergänzt. Testcontroller bzw. Stubs können ihre Testdaten bzw. Simulationsdaten aus Data-Files beziehen. Sowohl der Testcontroller als auch die Stubs können zusätzlich mit einer grafischen Oberfläche TestcontrollerDialog bzw. StubDialog versehen werden, um einen interaktiven Test zu ermöglichen (Beispiel TestDialog für grafische Objekte). Im Hinblick auf den erheblichen Aufwand eines interaktiven Regressionstests ist jedoch in der Regel ein codegesteuerter Test zu bevorzugen. Die Standardtestapplikation bindet den Tracer und den Testcontroller ein und ermöglicht deren Bedienung (Sie enthält z.B. einen Menüpunkt „Start Test“). 2.3.5.2.6.2 Durchführung eines kombinierten White und Black Box Test für Klassen Zur Vereinfachung der Testdurchführung und der Dokumentation können Black und White Box Tests kombiniert werden : An Hand der Klassendokumentation werden für den Black Box Test Äquivalenzklassen gebildet, Grenzwerte ermittelt und Spezialfälle überlegt. Die Testdaten werden zu Testfällen kombiniert und die Sollwerte überlegt. Die Testfälle werden zu Testsequenzen kombiniert. Es wird ein Testcontroller geschrieben, der den Code zur Durchführung der Testsequenzen enthält. Der Testcontroller erzeugt den Testling und ggf. Stubs, versetzt den Testling in den gewünschten State und stößt die zum Testfall gehörende Operation an. Nach der Ausführung der Operation werden mindestens die Output- Parameter und die relevanten States mittels Tracer in einem zum Testling gehörenden File <TestlingName>Test.log protokolliert. Zur Unterstützung des Tests müssen entsprechende Set() und Get() Funktionen für das Setzen und Auslesen der States vorhanden sein. Die Ergebnisse der Funktionsaufrufe werden mit den erwarteten Ergebnissen verglichen. Die zugehörigen .log Dateien dienen als Vorlage für spätere Regressionstests. Durch Codeinspektion wird ermittelt, welche wichtigen und kritischen Pfade und Bedingungen durch die funktionalen Testfälle noch nicht abgedeckt sind. Es werden Testfälle für die noch nicht durchlaufenen Pfade oder Bedingungen aufgestellt und dem Testcontroller hinzugefügt. 2.3.5.2.6.3 Durchführung eines Integrationstests für eine Gruppe von Klassen An Hand der Dokumentation der Use Case Realizations werden Testdaten für Testfälle überlegt. Es wird ein Testcontroller geschrieben, der den Code zur Durchführung der Testfälle enthält. 169 170 Der Testcontroller erzeugt die Instanzen der zu integrierenden Klassen und ggf. Stubs, versetzt die Instanzen in den gewünschten State und stößt die zum Testfall gehörenden Operationen an. Während der Interaktion der beteiligten Instanzen werden alle wichtigen Interaktionen vom Tracer in einem zum integrierten Subsystem gehörenden File <SubsystemName>Test.log protokolliert. Nach der Ausführung der Operation werden die Output- Parameter und die relevanten States protokolliert. Die zugehörigen .log Dateien dienen als Vorlage für spätere Regressionstests. Die aufgezeichneten Interaktionen sind mit dem für den Testfall zugrundeliegenden Interaktionsdiagramm zu vergleichen. Existiert kein solches Diagramm, so ist das aufgezeichnete Interaktionsszenario auf Plausibilität zu prüfen. Zur Vorbereitung des Integrationstests sind alle wichtigen öffentlichen Operationen aller wichtigen Klassen mit Traceaufrufen zu versehen, die im Falle eines Testlaufes über den Tracer durch Setzen des Trace Modus „Test“ aktiviert werden können. 2.3.5.2.6.4 Wer führt den Test durch Software-Entwicklung und Qualitätssicherung sind organisatorisch getrennt. Der Entwickler testet sein eigenes Programm. Auf diese Weise führt er sein eigenes Code Review durch. Die Abnahme durch die QS erfolgt erst nach dem Test. Der Package Owner testet die Klassen seines Packages. Der Package Owner integriert die Klassen seines Packages und die verwendeten Klassen anderer Packages. 2.3.5.2.6.5 Testendekriterium Das Ende der Tests ist mit einem fehlerfreien Durchlauf aller Testfälle erreicht. Bei sehr fehlerhafter Software entscheidet das Projekt-Team über die Weiterführung der Tests. 2.3.5.2.6.6 Verwaltung der gefundenen Fehler Gefundene Fehler werden priorisiert (medium, high, low), im Package Dokument verwaltet und entsprechend ihrer Bedeutung von dem Package Owner behoben. 2.3.5.2.6.7 Verwaltung der Testumgebung und Testdaten Alle für den Test benötigten Komponenten der Testumgebung, also der Testcontroller, die Stubs, das Logfile mit den Testergebnissen werden im Rahmen des Package des Testlings verwaltet. 2.3.5.3 Erstellen eines Test Case Ein Testfall beschreibt den Input, Conditions für die Ausführung, die erwarteten und die tatsächlichen Ergebnisse. Aus RUP The definition (usually formal) of a specific set of test inputs, execution conditions, and expected results, identified for the purpose of making an evaluation of some particular aspect of a Target Test Item. 170 171 2.3.5.4 Erstellen einer Test Prozedur Eine Test Prozedur beschreibt, wie die Evaluierung für einen Testfall durchzuführen ist. Z.B. die manuelle Durchführung eines Tests oder die Verwendung eines Testtools oder Testframeworks. Aus RUP A test procedure specifies how to perform one ore several test cases. How to perform one test case may be specified by one test procedure, but is often usful to reuse a test procedure a test procedure for several test cases. The step-by-step instructions that realize a test, enabling its execution. Test Scripts may take the form of either documented textual instructions that are executed manually or computer readable instructions that enable automated test execution. 2.3.5.5 Erstellen eines Test Log Ein TestLog File enthält die Ausgabe eines Testlaufs. Aus RUP A collection of raw output captured during a unique execution of one or more tests, usually representing the output resulting from the execution of a Test Suite for a single test cycle run 2.3.5.6 Erstellen einer Test Suite (vgl. JUnit) Eine Test Suite dient zur Gruppierung von Testfällen. (Bsp. Vgl Test Tool JUnit weiter unten) Aus RUP A package-like artifact used to group collections of Test Scripts, both to sequence the execution of the tests and to provide a useful and related set of Test Log information from which Test Results can be determined. 2.3.5.7 Unit Tests mit dem Test-Tool JUnit 2.3.5.7.1 Sinn und Zweck Ein Testtool automatisiert eine Test Prozedure oder Teile davon. Testtools werden oft mittels Scripting Language (Test Treiber, Test Skript) entwickelt. Sie stellen z.B. den Input bereit, führen den Test automatisch durch, zeichnen die Ergebnisse auf und vergleichen diese mit den erwarteten Ergebnissen. Beispiele sind JUnit, Cantata, Testat der Fa. IDAS, Siemens ITF, Visual Basic Test Script.) JUnit ist ein kleines Java-Framework zum Schreiben und Ausführen automatischer Unit Tests. Die Software ist frei und im Kern von Kent Beck und Erich Gamma geschrieben. Die Tests werden direkt in Java kodiert, sind selbstüberprüfend und damit wiederholbar. Testfälle können mit JUnit einfach organisiert und über eine Bedienungsoberfläche ausgeführt werden. Der Framework-Kern besteht aus neun Klassen und circa tausend Zeilen Code. 2.3.5.7.2 Download und Installation (Selbststudium) JUnit ist Open Source Software unter IBM Common Public License. Die aktuelle Version kann von SourceForge heruntergeladen werden : http://sourceforge.net/projects/junit/ 171 172 Die offizielle Homepage von JUnit ist erreichbar unter: http://www.junit.org Entsprechende Frameworks sind auch für alle anderen heute gängigen Programmiersprachen frei erhältlich: http://www.xprogramming.com/software.htm Das JUnit-Framework ist in einem JAR-Archiv namens junit.jar verpackt. Die vollständige Distribution besteht gegenwärtig aus einem ZIP-Archiv, in dem neben junit.jar auch seine Quellen (in src.jar), seine Tests, einige Beispiele, die Javadoc-Dokumentation, die FAQs, ein Kochbuch sowie zwei Artikel aus dem amerikanischen Java Report beiliegen. Zur Installation muss das ZIP-Archiv entpackt und junit.jar in den CLASSPATH übernommen werden. Hilfestellung gibt es ggf. im Archiv der JUnit-Yahoogroup : http://groups.yahoo.com/group/junit/ 2.3.5.7.3 Ausführung der Tests mittels TestRunner Zur Ausführung der Tests bietet JUnit drei verschiedene Gesichter : junit.swingui.TestRunner mit grafischer Swing-Oberfläche junit.awtui.TestRunner auf einfacher Basis des Java AWT junit.textui.TestRunner als Batch-Job auf der Text-Konsole Zur Ausführung kann man Testfallklassen und Testsuiteklassen mit vollklassifiziertem Namen eingeben und unter Swing auch aus dem Klassenpfad oder der Historie auswählen. Im mittleren Fenster werden die fehlschlagenden Tests aufgelistet, die man nach Mausklick im unteren Fenster mit Fehlermeldung und Stacktrace betrachten kann. Unter Swing kann man in dem mittleren Fenster zusätzlich den Testbaum öffnen und dann Testfälle und Testsuiten daraus auch einzeln ausführen. 172 173 Die rein textuelle Variante bietet eine Alternative zur grafischen Oberfläche, die besonders für den Testlauf zum Integrationszeitpunkt oder im nächtlichen Buildprozess geeignet ist. Die GUI-Oberflächen haben über der textuellen Darstellung den Vorteil, dass sie modifizierte Klassen automatisch neu laden können. 2.3.5.7.4 Wichtige Klassen des Frameworks // Einfügen Assert bietet Methoden zum Prüfen von Werten und Bedingungen. Eine Instanz der Klasse TestCase repräsentiert einen abstrakten Testfall Eine Instanz der Klasse EuroTest repräsentiert einen spezifischen Testfall Die Klasse EuroTest sammelt darüber hinaus alle Methoden, die ein bestimmtes Testobjekt, hier eine Instanz der Klasse Euro, testen sollen Die Klasse Euro repräsentiert das Testobjekt 2.3.5.7.5 SingleTestCase 2.3.5.7.5.1 Beispiel EuroTestSingleTestCase 2.3.5.7.5.1.1 Requirements Die Klasse Euro repräsentiert einen Geldbetrag. Ein Euro Objekt wird eindeutig durch seinen Wert beschrieben. Die Requirements für die Klasse können grob durch die Signatur der public Methoden beschrieben werden. public class Euro { public public public public double getAmount(){}; Euro add(Euro other) {}; String toString(){}; boolean equals(Object o) {}; } Ableitung der Testfälle aus den Requirements : Die Testfälle werden auf Basis der Requirements vor dem Code geschrieben. 2.3.5.7.5.1.2 Schreiben des Testfalles in Form der Testmethode public class EuroTest extends TestCase { public void testAmount() { Euro two = new Euro(2.00); assertTrue(2.00==two.getAmount()); } public static void main(String[] args) { junit.swingui.TestRunner.run(EuroTest.class); } } Die Tests werden getrennt von der Klasse Euro in einer Klasse namens EuroTest definiert. 173 174 Um die Testklasse in JUnit einzubinden, wird sie von dessen Framework-Basisklasse junit.framework.TestCase abgeleitet. Der eigentliche Testfall verbirgt sich hinter der Methode testAmount. Das Framework erkennt diese Methode automatisch als Testfall, weil sie der Konvention des Signaturmusters public void test...() folgt. Der Test erfolgt mit dem Aufruf der assertTrue Methode, die die Testklasse aus ihrer Oberklasse erbt. Das assertTrue Statement formuliert eine Annahme oder Forderung, die für den Code gilt und die JUnit automatisch verifiziert. Durch Ausführung der main Routine wird der JUnit-Test ausgeführt. Der junit.swingui.TestRunner stellt eine grafische Oberfläche auf Basis von Java Swing dar, um Unit Tests kontrolliert ablaufen zu lassen. 2.3.5.7.5.1.3 Schreiben der zu testenden Methode public class Euro { private long cents; public Euro(double euro) { this.cents=Math.round(euro*100.0); } public Euro(long cents) { this.cents=cents; } public double getAmount() { return cents/100.0; } } 2.3.5.7.5.2 Assert JUnit erlaubt, Werte und Bedingungen zu testen, die jeweils erfüllt sein müssen, damit der Test erfolgreich durchläuft. Die Klasse Assert definiert dazu eine Familie spezialisierter assert Methoden : assertTrue(boolean condition) ist die allgemeinste Zusicherung. Sie verifiziert, ob eine logische Bedingung wahr ist: assertTrue(theJungleBook.isChildrensMovie()); assertTrue(40 == xpProgrammer.workingHours()); Der Test ist erfolgreich, wenn die Bedingung erfüllt ist, das heißt der Ausdruck zu true ausgewertet werden konnte. Ist der Ausdruck dagegen false, protokolliert JUnit einen Fehlschlag. assertEquals(Object expected, Object actual) verifiziert, ob zwei Objekte gleich sind. Der Vergleich der Objekte erfolgt in JUnit über die equals Methode. assertEquals("foobar", "foo" + "bar"); assertEquals(new Euro(2.00), movie.getCharge(1)); Der Vorteil dieser und der noch folgenden assertEquals Varianten gegenüber dem Test mit assertTrue liegt darin, dass JUnit nützliche zusätzliche Informationen bieten kann, wenn der Test tatsächlich fehlschlägt. JUnit benutzt in diesem Fall die toString Repräsentation des Objekts, um den erwarteten Wert auszugeben. 174 175 assertEquals(int expected, int actual) verifiziert, ob zwei ganze Zahlen gleich sind. Der Vergleich erfolgt für die primitiven Java-Typen über den == Operator. assertEquals(40, xpProgrammer.workingHours()); assertEquals(double expected, double actual, double delta) verifiziert, ob zwei Fließkommazahlen gleich sind. Da Fließkommazahlen nicht mit unendlicher Genauigkeit verglichen werden können, wird zusätzlich eine Toleranz erwartet: assertEquals(3.1415, Math.pi(), 1e-4); assertNull(Object object) verifiziert, ob eine Referenz null ist: assertNull(hashMap.get(key)); assertNotNull(Object object) verifiziert, ob eine Objektreferenz nicht null ist: assertNotNull(httpRequest.getParameter("action")); assertSame(Object expected, Object actual) verifiziert, ob zwei Referenzen auf das selbe Objekt verweisen: assertSame(crouchingTigerHiddenDragon, movies.get(0)); Die assertEquals Methode ist neben den genannten Argumenttypen auch für die primitiven Datentypen float, long, boolean, byte, char und short überladen. assertFalse(boolean condition) verifiziert, ob eine Bedingung falsch ist. assertFalse(pulpFiction.isChildrensMovie()); Codebeispiel aus der Klasse Assert static public void assertEquals(String message, Object expected, Object actual) { if (expected == null && actual == null) return; if (expected != null && expected.equals(actual)) return; failNotEquals(message, expected, actual); } static private void failNotEquals(String message, Object expected, Object actual) { fail(format(message, expected, actual)); } static public void fail(String message) { throw new AssertionFailedError(message); } 2.3.5.7.5.3 AssertionFailedError Was passiert, wenn ein Test fehlschlägt? Die im Testcode durch assert Anweisungen kodierten Behauptungen werden von der Klasse Assert automatisch verifiziert. Im Fehlerfall bricht JUnit den laufenden Testfall sofort ab und wirft eine Ausnahme AssertionFailedError mit entsprechendem Fehlerprotokoll aus. Wenn die Fehlermeldung nicht ausreichen sollte, bietet JUnit für alle assert Methoden an, einen Erklärungstext zu tippen, der im Fehlerfall mitprotokolliert wird. assertTrue("Beträge stimmen nicht überein !", 1.00==two.getAmount()); 175 176 2.3.5.7.6 MultipleTestCases 2.3.5.7.6.1 Beispiel EuroTestMultipleTestCases public class EuroTest extends TestCase { private Euro two; protected void setUp() { two= new Euro(2.00); } protected void tearDown() { two= null; } public void testAmount() { assertTrue(2.00==two.getAmount()); } public void testRounding() { Euro rounded = new Euro(1.995); assertTrue("amount not rounded",2.00==rounded.getAmount()); assertEquals("rounding",2.00,rounded.getAmount(),0.001); assertEquals("rounding",two,rounded); } public void testAdding() { Euro sum = two.add(two); assertEquals("sum",4.00,sum.getAmount(),0.01); assertEquals("two",2.00,two.getAmount(),0.01); assertEquals("sum",new Euro(4.00),sum); assertEquals("two",new Euro(2.00),two); } public void testEqualty() { assertEquals(two,two); assertEquals(two,new Euro(2.00)); assertEquals(new Euro(2.00),two); assertFalse(two.equals(new Euro(7.00))); assertFalse(two.equals(null)); assertFalse(two.equals(new Object())); } public static void main(String[] args) { junit.swingui.TestRunner.run(EuroTest.class); } } 2.3.5.7.6.2 Definition von mehreren Testfällen in der Klasse EuroTest In der Regel beziehen sich mehrere Testfälle auf ein und dasselbe zu testende Objekt bzw. ein und dieselbe Menge von Testobjekten. Diese Menge von Testobjekten wird auch als Test-Fixture bezeichnet. 176 177 Diese Test-Fixture muss für jeden Testfall zunächst konstruiert und initialisiert werden. Testfälle, die sich auf die gleiche Test-Fixture beziehen, fasst man unter dem Dach der Testklasse EuroTest zusammen. (Alle Testfälle einer Testklasse sollten also von der gemeinsamen Fixture Gebrauch machen. Hat eine Testfallmethode keine Verwendung für die Fixture-Objekte, so ist dies meist ein guter Indiz dafür, dass die Methode auf eine andere Testklasse verschoben werden sollte. Testklassen sollten also um die Fixture organisiert werden, nicht um die getestete Klasse. Somit kann es auch durchaus vorkommen, dass zu einer Klasse mehrere korrespondierende Testfallklassen existieren, von denen jede ihre individuelle Fixture besitzt.) Damit fehlerhafte Testfälle nicht andere Testfälle beeinflussen können, läuft jeder Testfall im Kontext seiner eigenen Fixture. D.h. für jeden Testfall wird vor der Durchführung das zu testende Objekt neu initialisiert. Die Testobjekte werden als Instanzvariable der Testklasse definiert. Für Initialisierungs- bzw. Aufräumarbeiten kann man die Einschubmethoden des Frameworks setUp() bzw. tearDown() überschreiben. setUp() wird gerufen, bevor ein Testfall ausgeführt wird. tearDown() wird gerufen, nachdem ein Testfall ausgeführt wurde. Achtung : Die Fixture-Variablen werden im setUp() initialisiert, nicht etwa im Deklarationsteil oder im Konstruktor der Testfallklasse. 2.3.5.7.6.3 Generierung von EuroTest Instanzen für jeden Testfall Was passiert, wenn JUnit die Tests einer Klasse ausführt? JUnit unterscheidet im Prinzip zwischen zwei verschiedenen Phasen: Testfallerzeugung Das Test-Framework durchsucht die Testklasse EuroTest nach public Methoden mit der Signatur void test...(). Für jede gefundene test...() Methode erzeugt es jeweils ein eigenes Objekt der Klasse EuroTest und legt in diesem den Namen der test...() Methode ab. Die Testfallklasse EuroTest resultiert in so vielen Testfallobjekten, wie sie Testfallmethoden enthält. Auf diesen Objekten erfolgt dann der Testlauf: Testlauf Das Test-Framework durchläuft die Liste der EuroTest Objekte und ruft jeweils die darin gemerkte test...() Methode auf. Der Aufruf der test...() Methode wird durch den Aufruf vob setUp() und tearDown() gesandwiched. Genauer : Vor der Ausführung eines Testfalls wird jeweils erst die setUp Methode ausgeführt, sofern diese redefiniert wurde. Anschließend wird die den auszuführenden Testfall betreffende test... Methode gerufen. Abschließend wird noch die tearDown Methode ausgeführt, falls diese in der Testklasse redefiniert wurde. JUnit führt die gesammelten Testfälle also voneinander isoliert im Rahmen eigener Testobjekte aus, damit zwischen den einzelnen Testfällen keinerlei Seiteneffekte entstehen. Die Reihenfolge, in der Testfälle vom Framework ausgeführt werden, ist dabei prinzipiell undefiniert. Für JUnit sind die Testfälle vollkommen unabhängig voneinander: Sie besitzen keinen inhärenten Zustand, bauen sich selbst ihre Testumgebung auf, räumen hinter sich selbst auf und können in beliebiger Reihenfolge laufen. 177 178 Es dürfen keine Annahmen über die Reihenfolge im Testlauf getroffen werden. Voneinander abhängige Tests müssen stattdessen gemeinsam in einem Testfall ausgeführt werden. Darstellung des Sachverhaltes im Sequenzdiagramm // Einfügen 2.3.5.7.7 TestCaseTree 2.3.5.7.7.1 Beispiel AllTests public class AllTests { public static Test suite() { TestSuite suite=new TestSuite(); suite.addTestSuite(EuroTest.class); suite.addTestSuite(NameTest.class); return suite; } public static void main(String[] args) { junit.swingui.TestRunner.run(AllTests.class); } } 2.3.5.7.7.2 TestSuite Wie führt man eine Reihe von Tests zusammen aus? Mit JUnit kann man beliebig viele Tests in einer Testsuite zusammenfassen und gemeinsam ausführen. Um eine Testsuite zu erhalten, erzeugt man ein TestSuite Exemplar und fügt diesem mittels der addTestSuite() Methode die gewünschten Testfallklassen hinzu. public void addTestSuite(Class testClass) { addTest(new TestSuite(testClass)); } Der Code der addTestSuite() Methode lässt erkennen, dass für jede Testfallklasse implizit eine Suite erzeugt wird. Der Konstruktor von TestSuite generiert dann für jede test...() Methode eine Instanz der TestCase Klasse. Das Interface Test wird sowohl von TestCase als auch von TestSuite implementiert. Diese Technik erlaubt es, beliebig viele TestCase und TestSuite Objekte zu einer umfassenden Testsuite-Hierarchie, also einem TestCaseTree, zu kombinieren. // Einfügen Test abstrahiert von Testfällen und Testsuiten. TestSuite führt als Knoten des TestCaseTrees eine Reihe von Tests zusammen aus. TestCase repräsentiert als Leave einen ausführbaren Testfall Nach diesem Muster kann man dann auch Hierarchien von Testsuiten bilden: suite.addTest(database.AllTests.suite()); Die main Methode verschiebt sich von der EuroTest Testfallklasse in die AllTests Testsuiteklasse. 2.3.5.7.8 Exceptions 178 179 2.3.5.7.8.1 Testen von Exceptions Wie testen wir, ob eine Exception wie erwartet geworfen wird? // public Euro(double euro) { assert euro >=0.0 : "Negative amount"; //erst ab SDK 1.4 if (euro < 0.0) { throw (new IllegalArgumentException()); } this.cents=Math.round(euro*100.0); } public void testNegativeAmount() { try { final double NEGATIVE_AMOUNT=-2.00; new Euro(NEGATIVE_AMOUNT); fail("Should have raised an AssertionError"); } catch (IllegalArgumentException expected) { } } Wenn der Konstruktor den Fehler auslöst, kommt der catch Block zum Tragen. Der erwartete Fehler wird gefangen und alles ist gut. Löst der Konstruktor den Fehler dagegen nicht aus, läuft der Test zu weit, die fail Anweisung wird ausgeführt und JUnit protokolliert die Fehlermeldung. Wichtig: Um unter Java ab 1.4 asserts zu benutzen ist folgendes notwendig: Kompilieren: explizit dem Compiler per „Schalter“ zu Codegenerierung für Java 1.4 aufwärts zwingen: -source 1.4 Ausführen: hier ist auch ein Schalter zum Aktivieren der „asserts“ nötig: -ea um Asserts für die eigenen Klassen zu nutzen bzw. –esa um auch für die Javasystemklassen Asserts zu aktivieren. 2.3.5.7.8.2 Unerwartete Exceptions JUnit unterscheidet in seinem TestRunner zwischen einem »Failure« und einem »Error«: Ein Failure kennzeichnet eine fehlschlagende Zusicherung. Ein Error kennzeichnet ein unerwartetes Problem wie zum Beispiel eine NullPointerException, die im Test auch nicht behandelt werden soll. Die Ausnahmebehandlung von JUnit arbeitet wie folgt: Tritt im setUp ein Fehler auf, wird sowohl der Testfall als auch tearDown nicht mehr ausgeführt. Tritt in der test... Methode ein Fehler auf, wird trotzdem noch tearDown ausgeführt. Tritt im tearDown ein Fehler auf, gibt es keine weitere Rettung. 2.3.5.7.9 Implementationsdetails des Frameworks Aus Debug Session 179 180 AllTests public static void main(String[] args) { junit.swingui.TestRunner.run(AllTests.class); } TestRunner public static void run(Class test) { String args[]= { test.getName() }; main(args); } public static void main(String[] args) { new TestRunner().start(args); } public void start(String[] args) { String suiteName= processArguments(args); // Im Beispiel “AllTests” fFrame= createUI(suiteName); fFrame.pack(); fFrame.setVisible(true); if (suiteName != null) { setSuite(suiteName); runSuite(); } } synchronized public void runSuite() { if (fRunner != null) { fTestResult.stop(); } else { setLoading(shouldReload()); reset(); showInfo("Load Test Case..."); final String suiteName= getSuiteText(); final Test testSuite= getTest(suiteName); if (testSuite != null) { addToHistory(suiteName); doRunTest(testSuite); } } } suiteName ist “AllTests“ In getTest() wird über Reflection API gerufen AllTests public static Test suite() { TestSuite suite=new TestSuite(); suite.addTestSuite(EuroTest.class); suite.addTestSuite(NameTest.class); return suite; } Hier wird der Test-Tree aufgebaut 180 181 Aus addTestSuite() wird gerufen TestSuite public void addTest(Test test) { fTests.addElement(test); } fTests ist ein einfacher Vector Die Wurzel des Test Trees ist testSuite Die Analyse einer Leave TestCase Klasse erfolgt im Konstruktor der TestSuite Klasse : TestSuite public TestSuite(final Class theClass) { fName= theClass.getName(); try { getTestConstructor(theClass); // Avoid generating multiple error messages } catch (NoSuchMethodException e) { addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()")); return; } if (!Modifier.isPublic(theClass.getModifiers())) { addTest(warning("Class "+theClass.getName()+" is not public")); return; } Class superClass= theClass; Vector names= new Vector(); while (Test.class.isAssignableFrom(superClass)) { Method[] methods= superClass.getDeclaredMethods(); for (int i= 0; i < methods.length; i++) { addTestMethod(methods[i], names, theClass); } superClass= superClass.getSuperclass(); } if (fTests.size() == 0) addTest(warning("No tests found in "+theClass.getName())); } Mittels Reflection API werden zunächst die test...() Methoden der TestCase Klasse gesammelt private void addTestMethod(Method m, Vector names, Class theClass) { String name= m.getName(); if (names.contains(name)) return; if (! isPublicTestMethod(m)) { if (isTestMethod(m)) addTest(warning("Test method isn't public: "+m.getName())); return; } names.addElement(name); addTest(createTest(theClass, name)); } Für jede test..() Methode wird eine Instanz der Klasse TestCase erzeugt Nach dem Aufbau des Trees wird er ausgeführt 181 182 TestRunner private void doRunTest(final Test testSuite) { setButtonLabel(fRun, "Stop"); fRunner= new Thread("TestRunner-Thread") { public void run() { TestRunner.this.start(testSuite); postInfo("Running..."); long startTime= System.currentTimeMillis(); testSuite.run(fTestResult); if (fTestResult.shouldStop()) { postStatus("Stopped"); } else { long endTime= System.currentTimeMillis(); long runTime= endTime-startTime; postInfo("Finished: " + elapsedTimeAsString(runTime) + " seconds"); } runFinished(testSuite); setButtonLabel(fRun, "Run"); fRunner= null; System.gc(); } }; // make sure that the test result is created before we start the // test runner thread so that listeners can register for it. fTestResult= createTestResult(); fTestResult.addListener(TestRunner.this); aboutToStart(testSuite); fRunner.start(); } Vor dem Start wird der Test Tree in aboutToStart() grafisch dargestellt Der Testlauf erfolgt im Context eines eigenen Threads Die run() Funktion dieses Threads ruft nach einigen Vorbereitungen (z.B. Progressbalken) testSuite.run() TestSuite public void run(TestResult result) { for (Enumeration e= tests(); e.hasMoreElements(); ) { if (result.shouldStop() ) break; Test test= (Test)e.nextElement(); runTest(test, result); } } Diese Funktion ruft runTest() für alle Tests der Suite public void runTest(Test test, TestResult result) { test.run(result); } Diese Funktion ruft wiederum die run() Funktion der Tests TestCase 182 183 public void run(TestResult result) { result.run(this); } Im Falle eines Leaves wird schließlich result.run() gerufen TestResult protected void run(final TestCase test) { startTest(test); Protectable p= new Protectable() { public void protect() throws Throwable { test.runBare(); } }; runProtected(test, p); endTest(test); } TestCase public void runBare() throws Throwable { setUp(); try { runTest(); } finally { tearDown(); } } protected void runTest() throws Throwable { assertNotNull(fName); Method runMethod= null; try { // use getMethod to get all public inherited // methods. getDeclaredMethods returns all // methods of this class but excludes the // inherited ones. runMethod= getClass().getMethod(fName, null); } catch (NoSuchMethodException e) { fail("Method \""+fName+"\" not found"); } if (!Modifier.isPublic(runMethod.getModifiers())) { fail("Method \""+fName+"\" should be public"); } try { runMethod.invoke(this, new Class[0]); } catch (InvocationTargetException e) { e.fillInStackTrace(); throw e.getTargetException(); } catch (IllegalAccessException e) { e.fillInStackTrace(); throw e; } } 183 184 TestCase enthält den Namen der auszuführenden Funktion in der String Variablen fName (Jede Funktion wird ja im Context eines anderen TestCase Objektes ausgeführt!) Diese Funktion holt über Reflection API die gewünschte Funktion fName und führt sie aus. 2.3.6 Configuration&Change Management 2.3.6.1 Beispiel für Configuration Management Guidelines 2.3.6.1.1 Software-Element Ein Softwareelement ist jeder identifizierbare und maschinenlesbare Bestandteil des entstehenden Produktes. Ein Software-Element bildet eine atomare Einheit. Ein Atom enthält keine Untereinheiten, die unabhängig voneinander variieren. 2.3.6.1.2 Version Eine Version kennzeichnet die Ausprägung eines Software-Elementes zu einem bestimmten Zeitpunkt. Unter Versionen werden zeitlich nacheinander liegende Ausprägungen eines Software-Elementes verstanden. Eine Version wird in der Regel durch eine Nummer beschrieben. CVS Beim ersten Einchecken wird der Datei die Nummer 1.1 zugewiesen. Werden nun Modifikationen eingecheckt, so wird dieser Zähler an der zweiten Stelle erhöht, somit wird aus 1.1 die 1.2 usw. Diesen internen Zähler nennt CVS Revision, während man umgangssprachlich den Begriff Version verwendet. Datei D 1.1 1.2 1.3 1.4 Bild Perlenschnur 2.3.6.1.3 Checkin-Checkout Modell Der Reservierungsmechanismus sorgt dafür, das sich Änderungen nicht gegenseitig überschreiben. Eine Checkout Operation holt eine Kopie eines Software-Elementes aus einem Repository und reserviert es für den Ausbucher. 184 185 Nach der Überarbeitung befördert die Checkin Operation das Element in das Repository und löscht die Reservierung. Die Operation vermerkt zusätzlich den Autor, die Einbuchungszeit, usw. Ein eingebuchtes Element ist „eingefroren“. Jeder Checkin-Checkout Zyklus erzeugt ein neues Element. 2.3.6.1.4 Konfiguration (Lineup) und Release (Baseline) Eine Menge von Software-Elementen, die zusammen ein Produkt oder ein Zwischenergebnis darstellen, bezeichnet man als Konfiguration. Konfigurations repräsentieren signifikante Kombinationen von files. Eine Release ist eine konsistente Konfiguration des Systems. Sie bezeichnet eine benannte und formal freigegebene Menge von Software Elementen, die zu einem bestimmten Zeitpunkt in ihren Schnittstellen aufeinander abgestimmt sind und gemeinsam eine vorgesehene Aufgabe erfüllen. Sie ist getestet und archiviert. Sie ist Ausgangspunkt für weitere Systemerweiterungen. // Einfügen Bild Release Eine Konfiguration wird als Release markiert, indem man der Konfiguration einen symbolischen Namen z.B. „Release 1.1“ oder „Release Automat mit Flaschenrückgabe“ zuweist. Man labelt alle zur Konfiguration gehörenden Dokumente. Dadurch lässt sich auch nach einem längeren Zeitraum diese Release z.B. für eine Fehlerbeseitigung auschecken. 2.3.6.1.5 Configuration Specification (Konfigurations-Identifikationsdokument KID) Im KID wird für eine Konfiguration aufgeführt, welche Software-Elemente in welcher Version zu ihr gehören. In der Regel werden auch verwendete Tools aufgeführt. Bei Verwendung eines Tools mit Label-Möglichkeit erübrigt sich dieses Dokument. 2.3.6.1.6 Single Stream Versioning „latest is greatest“ Jeder Entwickler verwendet die jüngste Version der files. Zum Editieren eines files wird die jüngste Version ausgecheckt, die Änderung vorgenommen und das file wieder eingecheckt. Die Änderung ist sofort für alle Entwickler sichtbar. Vorteil Einfache Vorgehensweise Nachteil Die Änderung eines Files zwingt in der Regel viele Entwickler zur Änderung der eigenen Files. 2.3.6.1.7 Varianten von Versionen Unter Varianten werden zeitlich nebeneinander liegende Ausprägungen eines SoftwareElementes verstanden. 185 186 2.3.6.1.8 Branches Verschiedene Versionen eines Softwareelementes führen zu einem sequentiellen Versionsstamm. Dieser wird als main trunk bezeichnet. Eine Verzweigung dieses Stammes wird als branch bezeichnet. Z.B. soll ein vom Kunden gemeldeter Fehler in einem eigenen Branch behoben werden. // Einfügen Bild Branch Die Dateien des neuen Branches werden ebenfalls in Versionen (CVS : Revisionen) verwaltet, wobei sich jedoch die Numerierung ändert. Varianten von Versionen kann man folgendermaßen kennzeichnen : Variantennummer = release.level.branch.sequence CVS übernimmt die Revisions Nummer der jeweiligen Datei und fügt eine weiter Stelle an, wobei dieser die nächste freie gerade Zahl beginnend mit 2 zugewiesen wird. Erst die vierte Stelle wird bei jeder neue Revision inkrementiert. Die vierte Ziffer ist zu Beginn mit der 0 besetzt und besagt, dass diese Dateien identisch mit den vorherigen Revisionen (aus dem main trunk) sind. Erst wenn Änderungen an den Dateien des Branches vorgenommen und eingecheckt werden, wird die vierte Ziffer inkrementiert. Jede Datei die neu in einen Branch eingebunden wird und nicht aus einem anderen Branch (auch dem main trunk) stammt bekommt zwar die dritte Ziffer von dem aktuellen Branch (hier die 2) zugewiesen, doch wird bei der vierten Ziffer wieder bei 1 angefangen. 2.3.6.1.9 Parallel Stream Versioning Parallel Stream Versioning erlaubt jedem file ein branching tree von Versionen. In der Regel wird ein Zweig als integration branch deklariert. Dieser Zweig wird verwendet, um alle Änderungen zu sammeln und zu integrieren. Wenn eine configuration des integration branches gewisse Tests passiert hat, kann er als release (baseline) für die weitere Entwicklung zur Verfügung gestellt werden. Entwicklungsaktivitäten erfolgen also nicht direkt im Rahmen des integration branch sondern in separaten branches. Für jeden Entwickler existiert ein eigener branch. Nach Beendigung einer Entwicklungsaktivität werden die Änderungen mit dem integration branch gemerged. Branch 2 - Rechner 1.5.4.1 Main Trunk 1.1 Branch 1 - GUI 1.5.2.1 1.5.4.2 1.2 1.3 1.5.2.2 1.5.4.3 1.4 1.5.2.3 1.5 1.5.2.4 1.6 1.7 Symbolischer Name: Release-1 1.5.2.5 1.5.2.6 Bild Branch and Merge Vorteile Kontrollierte Releases (Baselines) Beide Entwickler setzen daher auf der jüngsten baseline auf. Die Zwischenversionen des jeweils anderen Entwicklers werden nicht sichtbar, da es sich um andere branches handelt. 186 187 Nachteile separater Integrationsschritt 2.3.6.1.10 File based System File based Systeme verlangen, dass der Anwender eine Kopie der benötigten files in einem lokalen Verzeichnis hat. z.B. CVS Control Version System Microsoft Visual source Save Clear Case with snapshot Views SCCS Source Code Control System 2.3.6.1.11 View based System Die Anwender arbeiten nicht direkt mit dem Inhalt des file systems. Sie verwenden stattdessen einen privaten Arbeitsbereich, der view genannt wird. Dieser view ermöglicht den Zugriff auf Versionen einer gewissen Menge von files. Eine configuration specification steuert die configuration für einen speziellen view. Der Anwender sieht die spezifizierten Versionen, als wären diese in einem gewöhnlichen file system abgelegt. z.B. Rational Clear Case 2.3.6.1.12 Zustandsübergänge bei der Bearbeitung eines Software-Elementes Zustände geplant in Bearbeitung QS vorgelegt akzeptiert Werden Änderungen an einem akzeptierten Software-Element vorgenommen, dann wird dessen Zustand von „akzeptiert“ auf „in Bearbeitung“ zurückversetzt. Die Versionsnummer wird inkrementiert. Nach der Bearbeitung wird es der Qualitätssicherung (QS) vorgelegt. Gibt die QS das Software-Element frei, so wird es in den Zustand „akzeptiert“ versetzt. Einen entsprechenden Zyklus durchläuft die gesamte Konfiguration. Das KID wird kopiert und in den Zustand „in Bearbeitung“ versetzt. Dabei wird die Versionsnummer erhöht. Wenn alle modifizierten Software-Elemente den Zustand „akzeptiert“ erreicht haben, wird das KID der QS vorgelegt. Nach der Freigabe durch die QS erreicht das KID den Zustand „akzeptiert“. 2.3.6.2 Beispiel Configuration Management mit CVS (Tutorial, Selbststudium) 2.3.6.2.1 Struktur des Projektes In dem folgenden Beispiel wird eine kleine Taschenrechner Applikation erstellt. Das Team besteht aus einem Projektleiter und 2 Entwickler. Der Projektleiter erstellt das Grundgerüst und speichert alles in dem CVS Projekt TaschenRechner. 187 188 Der Projektleiter organisiert das Projekt in Packages. Wobei folgende Struktur verwendet werden soll: Die Startklasse (Main Class) soll den Namen TaschenRechner.java tragen und im Hauptverzeichnis TaschenRechner liegen. Die GUI Klasse soll den Namen TaschenRechnerGUI.java tragen und im Unterverzeichnis gui liegen. Sie wird vom Entwickler E1 entwickelt. Der eigentliche Rechner soll ein Interface mit dem Namen Rechner.java implementieren und den Namen RechnerImpl.java besitzen. Beide Dateien sollen im Unterverzeichnis rechner liegen. Sie werden vom Entwickler E2 entwickelt. 2.3.6.2.2 Allgemeine Vorbereitungen 2.3.6.2.2.1 Download von CVS CVS gibt es für verschiedene Computersysteme. Es wurde ursprünglich für UNIX entwickelt. Für Windows gibt es eine Version für den reinen Kommandomodus. Unter http://www.cvshome.org/dev/codewindow.html kann ein ZIP Archiv geladen werden, das die eigentliche .exe Datei enthält. Diese .exe wird in ein beliebiges Verzeichnis extrahiert und nach dem Setzen des Programmpfades in die PATH Umgebungsvariable von Windows kann das Programm benutzt werden. Achtung : Aktuell ist die Version cvs-1-11-2.zip. Die aktuelle Version wurde nicht getestet. Bitte verwenden Sie die „alte“ Version von der HomePage Edelmann. 2.3.6.2.2.2 Installation von CVS Ordner CVS auf Laufwerk C anlegen Datei cvs.exe in dieses Verzeichnis extrahieren Setzen des Pfades als Admin, dann ist es auch für alle Nutzer gültig : Start -> Systemsteuerung -> System -> Umgebung -> Systemvariablen -> PATH Variable bearbeiten: folgenden Eintrag hinzufügen: ;C:\CVS Setzen des Pfades als User, dann ist es nur für diesen Nutzer gültig : Start -> Systemsteuerung -> System -> Umgebung -> Benutzervariablen -> PATH Variable bearbeiten: folgenden Eintrag hinzufügen: ;C:\CVS 2.3.6.2.2.3 Setzen des Classpath für den Taschenrechner Damit nachfolgendes Beispiel auch richtig kompiliert und ausgeführt werden kann, muss CLASSPATH wie folgt gesetzt werden : Für den Projektleiter werde Laufwerk E angenommen: set CLASSPATH=%CLASSPATH%;E:\TaschenRechner;E:\TaschenRechner\gui;E:TaschenRechner\rec hner (Das Setzen kann temporär in der Kommandobox erledigt werden) 2.3.6.2.2.4 Anlegen des Projektverzeichnisses Anlegen des Verzeichnisses auf Laufwerk E : 188 189 md E:\TaschenRechner Wechseln in dieses Verzeichnis : cd E:\TaschenRechner 2.3.6.2.2.5 Anlegen des Repository CVS benötigt für seine Arbeit eine Art Datenbank, in der es die Projekte verwaltet. Diese Datenbank wird Repository genannt und stellt sich auf der Festplatte als ein Ordner mit Verwaltungs-, Konfigurationsdateien und Unterverzeichnissen dar. Der Befehl hierzu lautet allgemein : cvs –d :local:<Verzeichnis>\<ReposName> init In unserem Beispiel : Auf Laufwerk E wechseln : md CVSRepos Erstellen des Repository : cvs –d:local:E:\CVSRepos\newRepos init 2.3.6.2.2.6 Setzen der CVSROOT Variablen set CVSROOT=:local:E:\CVSRepos\newRepos 2.3.6.2.2.7 Sonderbehandlung von Binärdateien einstellen Delta Speicherung Beim allerersten Einchecken des Projektes speichert CVS eine Kopie jeder Datei. Werden nun Veränderungen an der Datei vorgenommen und diese eingecheckt, so wird nicht wieder eine Dateikopie angelegt, sondern die Änderung (zusammen mit internen Befehlen) in die bereits bestehende Datei geschrieben. Der Vorteil liegt in dem eingesparten Speicherplatz und in dem schnellen Zugriff von CVS auf Änderungen. Ändern des Verhaltens bei Binärdateien CVS ist nicht in der Lage, Binärdateien mittels Delta-Speicherung abzulegen. Da jedoch auch solche Dateien unter Kontrolle von CVS gestellt werden sollen, muss man CVS beim Einchecken von Binärdateien mitteilen, dass es sich um eine Binärdatei handelt und daher keine Delta Speicherung verwendet werden soll. Damit nicht jedes mal beim Einchecken die Option „Binärdatei“ gesetzt werden muss, kann man Dateien mit bestimmten Namensendungen z.B. *.bin etc. in der CVS Steuerdatei cvswrappers als Binärdateien eintragen. Um diese Steuerdatei zu editieren müssen die sogenannten Administrationsdateien ausgecheckt werden. Dazu erstellt man sich einen Tempordner, wechselt in diesen und ruft cvs folgendermaßen auf : cvs –d <Pfad und ReposName> checkout CVSROOT. Im Beispiel : 189 190 Anlegen des Tempordners: md E:\t cd E: \t CVSROOT auschecken: cvs checkout CVSROOT Ändern der Datei cvswrappers (Änderungen sind rot markiert) # This file affects handling of files based on their names. # # The -t/-f options allow one to treat directories of files # as a single file, or to transform a file in other ways on # its way in and out of CVS. # # The -m option specifies whether CVS attempts to merge files. # # The -k option specifies keyword expansion (e.g. -kb for binary). # # Format of wrapper file ($CVSROOT/CVSROOT/cvswrappers or .cvswrappers) # # wildcard [option value][option value]... # # where option is one of # -f from cvs filter value: path to filter # -t to cvs filter value: path to filter # -m update methodology value: MERGE or COPY # -k expansion mode value: b, o, kkv, &c # # and value is a single-quote delimited value. # For example: *.gif -k 'b' *.class -k 'b' *.jpeg -k 'b' *.bin -k 'b' *.exe -k 'b' *.vep -k 'b' Achtung : Falls Word verwendet wird, Abspeicherung als Textfile ohne Extension .txt. Die Einträge sind je nach Bedürfnis anzupassen. Nach den Änderungen an den Administrationsdateien müssen diese wieder eingecheckt werden, damit CVS sich „umstellen“ kann. Der allgemeine Befehl zum Einchecken sieht wie folgt aus: cvs commit –m <“Kommentar“> <Projektname>. Im Beispiel : Einchecken : cvs commit –m „Bin File Status geändert“ Löschen des Tempordners : rd G:\t /S /Q. (/S = alle Dateien und Unterverzeichnisse; /Q = ohne Nachfrage) 2.3.6.2.3 Erstellen des Main Trunk durch den Projektleiter 2.3.6.2.3.1 Anlegen des Projektes 190 191 Das Anlegen des Projektes erfolgt mit dem Aufruf : cvs import –m <“Kommentar“> <Projektname> <StartInitial> start Durch den Aufruf wird das Projekt mit dem <Projektnamen> in das Repository „importiert“. Der Kommentar dient zur Charakterisierung des Eincheckvorganges und wird auch bei jedem weiteren Einchecken angeben (natürlich mit anderem Inhalt). Unter dem StartInitial ist eine Art Benutzerkennung zu verstehen. Sie kann z.B. aus dem Namenskürzel des Projektleiters bestehen. Im Beispiel : In das Verzeichnis TaschenRechner wechseln Anlegen des Projektes : cvs import -m "Start des Projektes mit dem Hauptverzeichnis" TaschenRechner cs start Löschen des Verzeichnisses : cd \ rd TaschenRechner /S /Q Anschließend ist noch das soeben angelegt Projekt auszuchecken, da nicht direkt im Repository gearbeitet wird, sondern in einer Arbeitskopie : Auschecken unseres Projektes : cvs checkout TaschenRechner 2.3.6.2.3.2 Erzeugen des Package rechner Das Package rechner wird durch Anlegen des gleichnamigen Verzeichnisses innerhalb des Projektverzeichnisses erreicht: cd TaschenRechner md rechner Nun muss noch dieses unter die Kontrolle von CVS gestellt werden. Neue Verzeichnisse innerhalb des Projektes werden mittels cvs add <Dateiname oder Ordnername> aufgenommen. Im Beispiel : cvs add rechner Achtung : Dateien in diesem neuen Verzeichnis werden nicht automatisch mit unter die Kontrolle von cvs gestellt ! Die Dateien müssen zusätzlich einzeln aufgenommen werden. 2.3.6.2.3.3 Erzeugen des Interfaces Nun wird die Datei Rechner.java, die dass Interface darstellt in diesem Verzeichnis erzeugt : // Rechner.java package rechner; public interface Rechner { public void setOperand(double operand); 191 192 public void setOperator(char operator); public double getErgebnis(); } 2.3.6.2.3.4 Erzeugen einer Dummy Implementation des Interfaces // RechnerImpl.java package rechner; public class RechnerImpl implements Rechner { public void setOperand(double operand) { } public void setOperator(char operator) { } public double getErgebnis() { return 7; //beliebiger DummyRückgabeWert } public RechnerImpl() { } } 2.3.6.2.3.5 Aufnehmen der Dateien in das Projekt cvs add Rechner.java cvs add RechnerImpl.java 2.3.6.2.3.6 Einchecken der Dateien cvs commit –m „Package rechner und Rechner.java und RechnerImpl.java aufgenommen“ Anmerkung : Es werden alle Files eingecheckt. Die Files können auch einzeln eingecheckt werden. Verzeichnisse werden nicht mittels commit eingeckeckt sondern nur mittels add hinzugefügt. 2.3.6.2.3.7 Labeln der Konfiguration Nun wird diesem Projektzustand ein symbolischer Name gegeben, um diesen speziellen Projektzustand wieder auschecken zu können. cvs tag PackageRechnerUndInterface 2.3.6.2.3.8 Erzeugen das Package gui und der Datei TaschenRechnerGUI Das Anlegen und Aufnehmen geschieht analog zu dem obigem Package rechner : // TaschenRechnerGUI.java 192 193 package gui; import rechner.Rechner; import rechner.RechnerImpl; public class TaschenRechnerGUI { private Rechner tr = null; public TaschenRechnerGUI() { tr=new RechnerImpl(); } } Aufnehmen vom Projekthauptverzeichnis aus : md gui cvs add gui cvs .\gui\add TaschenRechnerGUI.java cvs commit –m „Package gui sowie Datei TaschenRechnerGUI aufgenommen“ Vergeben des symbolischen Namens PackageGUIundGUIklasse cvs tag PackageGUIundGUIklasse 2.3.6.2.3.9 Erzeugen der Main Klasse TaschenRechner.java Damit das Projekt auch gestartet werden kann, muss noch eine Startklasse mit einer main Funktion erstellt werden. Die Datei Rechner.java wird im Projekthauptverzeichnis gespeichert : //TaschenRechner.java import gui.TaschenRechnerGUI; public class TaschenRechner { public static void main(String args[]) { TaschenRechnerGUI rechner=new TaschenRechnerGUI(); } } Nun wird auch diese Datei eingecheckt und dem Projekt der symbolische Name MainTrunkVorBranchBildung vergeben : cvs add TaschenRechner.java cvs commit –m „Startklasse Rechner.java erzeugt und aufgenommen“ cvs tag MainTrunkVorBranchBildung 2.3.6.2.3.10 Anlegen der Branches Der Projektleiter ist nun mit dem Maintrunk fertig und teilt das Projekt den beiden Entwicklern zu. Damit beide unabhängig und ohne Störungen arbeiten können, werden von dem Maintrunk 2 Branches angelegt. Branch 1 für das GUI und Branch 2 für den eigentlichen Rechner. E1 ist für die GUI und E2 für den Rechner zuständig. Auschecken der letzten lineup (Revision) : cvs checkout TaschenRechner In die Arbeitskopie wechseln : 193 194 cd TaschenRechner Von dieser Arbeitskopie (die auf dem neusten Stand ist) ausgehend den neuen Branch anlegen: cvs tag –b Entwickler-1-GUI Mit diesem Befehl wird im Repository ein neuer Branch unter dem Namen Entickler-1-GUI angelegt. Die aktuell ausgecheckte Arbeitskopie ist noch nicht aus diesem neuen Branch. Deshalb kann nun im nächsten Schritt auch gleich der zweite Branch erzeugt werden. Anlegen von Branch 2: rechner cvs tag –b Entwickler-2-Rechner 2.3.6.2.3.11 Zugriff auf zentrales Repository ermöglichen Entwickler 1 und 2 müssen Zugriff auf das zentrale Repository haben. Dies gelingt ggf. unter Verwendung von Netzlaufwerken. 2.3.6.2.4 Entwickeln der GUI durch Entwickler1 Der Entwickler 1 checkt nun seinen Zweig des Projektes aus und erhält dadurch eine Arbeitskopie aus seinem Branch. Hiermit entwickelt er die GUI Klasse TaschenRechnerGUI.java und erstellt zum Testen auch eine kleine Testversion von TaschenRechnerImpl.java. Nach der Fertigstellung und dem Test kann der Projektleiter die GUI Klasse wieder in den Maintrunk überführen. 2.3.6.2.4.1 Auschecken der Arbeitskopie vom Branch Entwickler-1-GUI Allgemein : cvs checkout –r <BranchName> <ProjektName> Im Beispiel : cvs checkout –r Entwickler-1-GUI TaschenRechner Die Arbeitskopie ist jetzt noch auf Stand der letzten MainTrunk Version des Projektes. 2.3.6.2.4.2 Codieren der GUI Klasse In diesem einfachen Besipiel wird davon ausgegangen, dass der Entwickler die GUI Klasse in einem Stück schreibt und somit auch nur einmal eincheckt. In der Regel wird er seine Klasse jedoch in mehreren Schritten entwickeln und jede einzelne Version einchecken. //Source file: P:\\TaschenRechner\\gui\\TaschenRechnerGUI.java package gui; import import import import import java.awt.*; java.awt.event.*; javax.swing.*; rechner.Rechner; rechner.RechnerImpl; 194 195 public class TaschenRechnerGUI extends JFrame implements ActionListener { private Rechner tr = null; private JButton operatorPlus = new JButton (new ("/TaschenRechner/gui/OperatorPlus.gif")); private JButton operatorMinus = new JButton (new ("/TaschenRechner/gui/OperatorMinus.gif")); private JButton operatorMal = new JButton (new ("/TaschenRechner/gui/OperatorMal.gif")); private JButton operatorDurch = new JButton (new ("/TaschenRechner/gui/OperatorDurch.gif")); private JButton operatorGleich = new JButton (new ("/TaschenRechner/gui/OperatorGleich.gif")); private JTextField ausgabeFenster = new JTextField (20); private JButton operand0 = new JButton (new ("/TaschenRechner/gui/Operand0.gif")); private JButton operand1 = new JButton (new ("/TaschenRechner/gui/Operand1.gif")); private JButton operand2 = new JButton (new ("/TaschenRechner/gui/Operand2.gif")); private JButton operand3 = new JButton (new ("/TaschenRechner/gui/Operand3.gif")); private JButton operand4 = new JButton (new ("/TaschenRechner/gui/Operand4.gif")); private JButton operand5 = new JButton (new ("/TaschenRechner/gui/Operand5.gif")); private JButton operand6 = new JButton (new ("/TaschenRechner/gui/Operand6.gif")); private JButton operand7 = new JButton (new ("/TaschenRechner/gui/Operand7.gif")); private JButton operand8 = new JButton (new ("/TaschenRechner/gui/Operand8.gif")); private JButton operand9 = new JButton (new ("/TaschenRechner/gui/Operand9.gif")); private JButton operandP = new JButton (new ("/TaschenRechner/gui/OperandPunkt.gif")); ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon ImageIcon public TaschenRechnerGUI() { super("CVS - Testprojekt: Taschenrechner"); setSize(460,300); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); tr=new RechnerImpl(); Container content=this.getContentPane(); content.setLayout(new FlowLayout()); operatorPlus.setActionCommand("plus"); operatorPlus.addActionListener(this); operatorMinus.setActionCommand("minus"); operatorMinus.addActionListener(this); operatorMal.setActionCommand("mal"); operatorMal.addActionListener(this); operatorDurch.setActionCommand("durch"); operatorDurch.addActionListener(this); operatorGleich.setActionCommand("gleich"); operatorGleich.addActionListener(this); operand0.setActionCommand("0"); operand0.addActionListener(this); operand1.setActionCommand("1"); operand1.addActionListener(this); operand2.setActionCommand("2"); operand2.addActionListener(this); operand3.setActionCommand("3"); operand3.addActionListener(this); operand4.setActionCommand("4"); operand4.addActionListener(this); operand5.setActionCommand("5"); operand5.addActionListener(this); operand6.setActionCommand("6"); operand6.addActionListener(this); operand7.setActionCommand("7"); 195 196 operand7.addActionListener(this); operand8.setActionCommand("8"); operand8.addActionListener(this); operand9.setActionCommand("9"); operand9.addActionListener(this); operandP.setActionCommand("."); operandP.addActionListener(this); content.add(operatorPlus); content.add(operatorMinus); content.add(operatorMal); content.add(operatorDurch); content.add(operatorGleich); content.add(operand0); content.add(operand1); content.add(operand2); content.add(operand3); content.add(operand4); content.add(operand5); content.add(operand6); content.add(operand7); content.add(operand8); content.add(operand9); content.add(operandP); content.add(ausgabeFenster); this.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent we) { System.exit(0); }}); this.setVisible(true); } private void setOperand(char operand) { String szahl=ausgabeFenster.getText(); ausgabeFenster.setText(szahl+operand); szahl=ausgabeFenster.getText(); tr.setOperand(Double.valueOf(szahl).doubleValue()); } private void setOperator(char operator) { tr.setOperator(operator); ausgabeFenster.setText(""); } private void ausgabeOperator(char operator) { try { tr.setOperator(operator); //da jetzt berechnet wird, kann Exception geworfen werden } catch (java.lang.ArithmeticException e) { String message=e.getMessage(); if (message.equals("/ by zero")) System.out.println("Division durch Null!"); } double ergebnis=tr.getErgebnis(); ausgabeFenster.setText(String.valueOf(ergebnis)); } private void punktOperand(char operator) { String szahl=ausgabeFenster.getText(); if (szahl.equals("")==true) ausgabeFenster.setText("0."); else ausgabeFenster.setText(szahl+'.'); } public void actionPerformed(ActionEvent e) { String command=e.getActionCommand(); char operand=command.charAt(0); if (command.equals("plus")) setOperator('+'); 196 197 else if (command.equals("minus")) setOperator('-'); else if (command.equals("mal")) setOperator('*'); else if (command.equals("durch")) setOperator('/'); else if (command.equals("gleich")) ausgabeOperator('='); else switch(operand) { case '0':case '1':case '2':case '5':case '6':case '7':case'8':case'9': setOperand(operand); break; case '.': punktOperand(operand); } } } '3':case '4':case Diese Datei unterscheidet sich völlig von der Version aus dem MainTrunk. 2.3.6.2.4.3 Einchecken der Arbeitskopie in den Branch Entwickler-1-GUI cvs commit –m „GUI komplett fertiggestellt!“ cvs tag GetestetetVersion-Release1 2.3.6.2.5 Entwickeln des Rechners durch Entwickler2 2.3.6.2.5.1 Auschecken der Arbeitskopie vom Branch Entwickler-2-Rechner cvs checkout –r Entwickler-2-Rechner TaschenRechner 2.3.6.2.5.2 Codieren der Rechnerlogik – Datei TaschenRechnerImpl.java //Source file: P:\\Rechner\\rechner\\RechnerImpl.java package rechner; public class RechnerImpl implements Rechner { private double ergebnis; private double operand1 = 0; private double operand2 = 0; private char operator; private boolean operandOneEmpty; private boolean operatorEmpty; public void setOperand(double operand) { if (operandOneEmpty==true) { //noch keine Zahl eingegeben operand1=operand; } else //jetzt die 2te Zahl abspeichern operand2=operand; } public void setOperator(char operator) { if (operator=='=') { operandOneEmpty=true; berechne(); } else { this.operator=operator; operandOneEmpty=false; 197 198 operatorEmpty=false; } } public double getErgebnis() { if (operatorEmpty==false) { //nur wenn Rechenoperation gewählt wurde Rechne operatorEmpty=true; operatorEmpty=true; operand1=ergebnis; //für den Fall, daß das Ergebnis aus einer vorherigen Rechnung direkt weiter verwendet wird und somit nach dem =OPerator ein anderer Operator folgt return ergebnis; } else return operand1; } public RechnerImpl() { operandOneEmpty=true; operatorEmpty=true; } private void berechne() throws java.lang.ArithmeticException { switch (this.operator) { case '+': this.ergebnis=operand1+operand2; break; case '-': ergebnis=operand1-operand2; break; case '*': ergebnis=operand1*operand2; break; case '/': { if (operand2==0.0) { ergebnis=0; //nicht ganz richtig (Divsision durch Null) throw (new java.lang.ArithmeticException("/ by zero")) ; } ergebnis=operand1/operand2; //wenn oben keine Exception->alles OK-> berechne } } } } 2.3.6.2.5.3 Codieren der GUI Klasse als Testtreiber Jetzt wird die TaschenRechnerGUI für einen kleinen Testlauf umgebaut (dies geschieht nur in diesem Branch und hat somit keinen Einfluss auf die gleichnamige Klasse von dem GUIBranch). // TaschenRechnerGUI.java -> nur zum Testen der RechnerLogik package gui; import rechner.Rechner; import rechner.RechnerImpl; public class TaschenRechnerGUI { private Rechner tr = null; public TaschenRechnerGUI() { tr=new RechnerImpl(); rechne(); } private void rechne() { double erg=0; //Addition tr.setOperand(12); tr.setOperator('+'); tr.setOperand(2); tr.setOperator('='); erg=tr.getErgebnis(); System.out.println("Addition: 12+2 = (Soll: 14) Ist: "+erg); //Subtraktion tr.setOperand(0.5); tr.setOperator('-'); tr.setOperand(12.5); 198 199 tr.setOperator('='); erg=tr.getErgebnis(); System.out.println ("Subtraktion: 0.5-12.5 = (Soll: -12) Ist: "+erg); //Multiplikation tr.setOperand(24.5); tr.setOperator('*'); tr.setOperand(2); tr.setOperator('='); erg=tr.getErgebnis(); System.out.println ("Multiplikation: 24.5*2 = (Soll: 49) Ist: "+erg); //Division tr.setOperand(27.5); tr.setOperator('/'); tr.setOperand(5.5); tr.setOperator('='); erg=tr.getErgebnis(); System.out.println ("Division: 27.5/5.5 = (Soll: 5) Ist: "+erg); //Division durch Null tr.setOperand(34); tr.setOperator('/'); tr.setOperand(0); try { tr.setOperator('='); erg=tr.getErgebnis(); System.out.println ("Division: 34/0= "+erg); } catch (java.lang.ArithmeticException e) { String message=e.getMessage(); if (message.equals("/ by zero")) System.out.println ("Division durch Null! "+tr.getErgebnis()); else System.out.println ("Division: 34/0= "+erg); } Division: 34/0= //Addition+Multiplikation tr.setOperand(4.5); tr.setOperator('+'); tr.setOperand(5.5); tr.setOperator('='); erg=tr.getErgebnis(); System.out.println ("Addition+Multiplikation: Teil 1: 4.5+5.5 = (Soll: 10) Ist: "+erg); tr.setOperator('*'); tr.setOperand(2); tr.setOperator('='); erg=tr.getErgebnis(); System.out.println ("Addition+Multiplikation: Teil 2: 10*2 = (Soll: 20) Ist: "+erg); } } 2.3.6.2.5.4 Einchecken der Arbeitskopie in den Branch Entwickler-2-Rechner Zum Schluss noch einchecken und einen sym. Namen vergeben cvs commit –m „Rechnerlogik gebaut und getestet“ cvs tag GetestetetRechnerVersion-Release1 2.3.6.2.6 Zusammenführen (Mergen) der Entwicklungsstränge durch den Projektleiter Ggf. TaschenRechner Verzeichnis löschen : rd TaschenRechner /s /q Auschecken des aktuellen Zustandes des Maintrunks : cvs checkout TaschenRechner In das Arbeitsverzeichnis wechseln : cd \TaschenRechner Package gui in den Maintrunk mergen : cvs update –j Entwickler-1-GUI gui Package rechner in den Maintrunk mergen : cvs update –j Entwickler-2-Rechner rechner 199 200 Einchecken der Änderungen : cvs commit –m „Die Packages gui und rechner der beiden Branches mit dem Maintrunk zusammengeführt“ Vergabe des symbolischen Namens : cvs tag Release-1 2.3.6.2.7 Darstellung der Entwicklungsstränge CVS bietet die Möglichkeit, sich einen Überblick über den Entwicklungsstand des Projektes anhand der eingecheckten Dateien und deren Revisionen zu machen. Mit dem Befehl cvs log, ausgeführt im Projekthauptverzeichnis, wird die Logdatei auf dem Bildschirm ausgegeben. Damit eine Datei erstellt wird leitet man die Ausgabe einfach in eine Datei um. Mit cvs log > log.txt wird die Logdatei log.txt im Projekthauptverzeichnis erzeugt. Die folgenden Beispielausgaben dokumentieren einen etwas komplexeren Entwicklungsprozess. Der Code wurde in mehreren Entwicklungsstufen erzeugt und eingecheckt. Dadurch ergeben sich für die einzelnen Dateien höhere Revisionsnummern. RCS file: p:\CVSRepos\newRepos/TaschenRechner/Rechner.java,v Working file: Rechner.java head: 1.1 branch: locks: strict access list: symbolic names: MainTrunkVorBranchBildung: 1.1 keyword substitution: kv total revisions: 1; selected revisions: 1 description: ---------------------------revision 1.1 date: 2002/05/07 19:50:21; author: shikar; state: Exp; Startklasse Rechner.java erzeugt und aufgenommen ============================================================================= RCS file: p:\CVSRepos\newRepos/TaschenRechner/Taschenrechner.mdl,v Working file: Taschenrechner.mdl head: 1.5 branch: locks: strict access list: symbolic names: MainTrunkVorBranchBildung: 1.5 PackageGUIundGUIklasse: 1.4 TaschenRechnerImpl: 1.3 PackageRechnerUndInterface: 1.2 UseCaseInit: 1.1 keyword substitution: kv total revisions: 5; selected revisions: 5 description: ---------------------------revision 1.5 date: 2002/05/07 19:50:22; author: shikar; state: Exp; lines: +419 -257 Startklasse Rechner.java erzeugt und aufgenommen ---------------------------revision 1.4 date: 2002/05/07 04:47:10; author: shikar; state: Exp; lines: +455 -788 Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt ---------------------------revision 1.3 date: 2002/05/07 04:27:27; author: shikar; state: Exp; lines: +270 -10 Datei TaschenRechnerImpl.java und UML erzeugt ---------------------------revision 1.2 date: 2002/05/07 04:07:45; author: shikar; state: Exp; lines: +243 -3 Package rechner und TaschenRechner.java aufgenommen ---------------------------revision 1.1 date: 2002/05/07 04:02:09; author: shikar; state: Exp; UseCase Diagramme erzeugt und aufgenommen ============================================================================= 200 201 RCS file: p:\CVSRepos\newRepos/TaschenRechner/gui/TaschenRechnerGUI.java,v Working file: gui/TaschenRechnerGUI.java head: 1.2 branch: locks: strict access list: symbolic names: MainTrunkVorBranchBildung: 1.2 PackageGUIundGUIklasse: 1.1 keyword substitution: kv total revisions: 2; selected revisions: 2 description: ---------------------------revision 1.2 date: 2002/05/07 19:50:22; author: shikar; state: Exp; lines: +3 -3 Startklasse Rechner.java erzeugt und aufgenommen ---------------------------revision 1.1 date: 2002/05/07 04:47:10; author: shikar; state: Exp; Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt ============================================================================= RCS file: p:\CVSRepos\newRepos/TaschenRechner/rechner/TaschenRechner.java,v Working file: rechner/TaschenRechner.java head: 1.1 branch: locks: strict access list: symbolic names: MainTrunkVorBranchBildung: 1.1 PackageGUIundGUIklasse: 1.1 TaschenRechnerImpl: 1.1 PackageRechnerUndInterface: 1.1 keyword substitution: kv total revisions: 1; selected revisions: 1 description: ---------------------------revision 1.1 date: 2002/05/07 04:07:46; author: shikar; state: Exp; Package rechner und TaschenRechner.java aufgenommen ============================================================================= RCS file: p:\CVSRepos\newRepos/TaschenRechner/rechner/TaschenRechnerImpl.java,v Working file: rechner/TaschenRechnerImpl.java head: 1.2 branch: locks: strict access list: symbolic names: MainTrunkVorBranchBildung: 1.2 PackageGUIundGUIklasse: 1.2 TaschenRechnerImpl: 1.1 keyword substitution: kv total revisions: 2; selected revisions: 2 description: ---------------------------revision 1.2 date: 2002/05/07 04:47:10; author: shikar; state: Exp; lines: +7 -5 Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt ---------------------------revision 1.1 date: 2002/05/07 04:27:28; author: shikar; state: Exp; Datei TaschenRechnerImpl.java und UML erzeugt Was sagt diese Datei nun aus? Es werden alle Dateien des Projektes mit den Erstellungs- und Änderungsinformationen, sowie den symbolischen Namen und der zugehörigen Revisionsnummer aufgelistet. Im Einzelnen betrachtet: Rechner.java: Aktuelle Revsion: 1.1 Anzahl der gesamten Revisionen: 1 Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 19:50:21 o Zugehöriger Info Text: Startklasse Rechner.java erzeugt und aufgenommen 201 202 o Vorhanden in der Projektversion mit dem symbolischen Namen MainTrunkVorBranchBildung TaschenRechnerGUI.java Aktuelle Revsion: 1.2 Anzahl der gesamten Revisionen: 2 Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:47:10 o Zugehöriger Info Text: Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt o Vorhanden in der Projektversion mit dem symbolischen Namen PackageGUIundGUIklasse Revision: 1.2 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 19:50:22 Zugehöriger Info Text: Startklasse Rechner.java erzeugt und aufgenommen o Vorhanden in der Projektversion mit dem symbolischen Namen MainTrunkVorBranchBildung TaschenRechner.java Aktuelle Revsion: 1.1 Anzahl der gesamten Revisionen: 1 Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:07:46 o Zugehöriger Info Text: Package rechner und TaschenRechner.java aufgenommen Vorhanden in der Projektversion mit dem symbolischen Namen MainTrunkVorBranchBildung; PackageGUIundGUIklasse; TaschenRechnerImpl;; PackageRechnerUndInterface TaschenRechnerImpl.java Aktuelle Revsion: 1.2 Anzahl der gesamten Revisionen: 2 Revision: 1.1 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:27:28 o Zugehöriger Info Text: Datei TaschenRechnerImpl.java und UML erzeugt o Vorhanden in der Projektversion mit dem symbolischen Namen TaschenRechnerImpl Revision: 1.2 Erstellungsdatum: 2002/05/07 (eng. Darstellung ) um 04:47:10 o Zugehöriger Info Text: Package gui sowie Datei und UML von TaschenRechnerGUI erzeugt o Vorhanden in der Projektversion mit dem symbolischen Namen MainTrunkVorBranchBildung; PackageGUIundGUIklasse 2.3.6.3 Beispiel Configuration Management Unterstützung durch Rational Rose 2.3.6.3.1 Controlled Units Per Default speichert Rose das gesamte Model in einem einzigen .mdl File. Parallele Entwicklung wird durch die Zerlegung des Files in eine Menge von individuellen Files, sogenannte controlled units, ermöglicht. controlled units sind die configuration elements für die Versionskontrolle. Jedes Team ist für die Entwicklung und Wartung einer spezifischen unit verantwortlich. Im Falle einer controlled unit enthält nicht mehr das .mdl file den Inhalt des Packages, sondern z.B. das .cat file. Das .mdl file enthält lediglich Referenzen auf die controlled units. Falls die controlled units weitere controlled units enthalten, besitzen sie ebenfalls wiederum Referenzen auf die enthaltenen controlled units. Die Struktur muss streng hierarchisch sein. 2.3.6.3.2 Verwalten von controlled units 202 203 Erzeugen : Markieren des package file unit control Speichern des übergeordneten controlled units sichert die Referenzen auf die neu erzeugten controlled units. Im Falle einer Versionskontrolle, muss die übergeordnete controlled unit vor dem Speichern ausgecheckt werden. D.h. für die übergeordnete controlled unit ensteht eine neue Version. Laden : file units load Updaten : file units reload Importieren file import Uncontrol file units uncontrol Beim Laden einer controlled unit aus einem read only file wird diese unit in rose als writeprotected deklariert. Eine checked-in unit ist daher automatisch write-protected im Gegensatz zu einer checkedout unit. Das Importieren einer controlled unit fügt dem model lediglich eine Referenz auf das zugehörige file hinzu. Uncontrol fügt den inhalt des zur unit gehörenden files in das file der umschließenden unit ein. Das file der umschließenden unit muss zuvor ausgechecked werden. 2.3.6.4 Bereitstellen eines Project Repository Z.B. mittels CVS 2.3.6.5 Erstellen von Base Lines Z.B. mittels CVS 2.3.6.6 2.3.7 2.3.7.1 Erstellen von Change Requests Deployment Erstellen von Training Material 203 204 Overhead slides for classroom teaching. Teacher's instructions. Example programs, databases, and so on. Textbooks, tutorials 2.3.7.2 Erstellen von User Support Material User Guides Maintenance Guides Online demos Online help system Context-sensitive help 2.3.7.3 Erstellen von Release Notes Release Notes identify changes and known bugs in a version of a build or deployment unit that has been made available for use. 2.3.7.4 Ausliefern an ß-Tester It is a good idea for a company to keep a database of potential beta reviewers and an archive of their feedback. Create a beta program to solicit feedback on the product under development from a subset of the intended users 2.3.8 2.3.8.1 Project Management Erstellen einer Risk List A sorted list of known and open risks to the project, sorted in decreasing order of importance and associated with specific mitigation or contingency actions. 2.3.8.2 Erstellen eines Iteration Plan Z.B. mit MS Project 2.3.8.3 Aquire Staff The Project Manager will have determined the staffing needs for the iteration and will look to the Human Resources function of the organization to provide staff with the needed domain, skills and experience profiles. Most organizations do not have the luxury of keeping a large pool of staff on stand-by for projects, and project starts do not always neatly synchronize with the termination of previous projects. Frequently then, except for a few staff engaged on the project from the outset, many will need to be hired. This may be a lengthy process, so the prudent Project Manager will always be looking ahead, and initiating the acquisition of staff for future iterations as well as the current one. It may be possible to cover shortfalls by working overtime or by the use of contract rather than permanent staff. 2.3.8.4 Erstellen von Work Orders The work order is the Project Manager's means of communicating what is to be done, and when, to the responsible staff. It becomes an internal contract between the Project Manager and those assigned responsibility for completion. 204 205 2.3.9 2.3.9.1 Environment Erstellen von Templates Z.B. Template für Vision, Risk List, Glossary 2.3.9.2 Erstellen von Guidelines Z.B. Programming Guidelines, Test Guidelines 2.3.9.3 Bereitstellen von Tools Z.B. Eclipse, Rational Rose, CVS 2.4 Phasen des RUP 2.4.1 Zitate zum Begriff Komplexität Autor unbekannt Ein Arzt, ein Hochbauingenieur und ein Informatiker unterhielten sich darüber, was der älteste Beruf der Welt sei. Der Mediziner führte an, "Schon in der Bibel heißt es, dass Gott Eva aus Adams Rippe erschaffen hat. Dafür wurde natürlich die Medizin gebraucht, und so darf ich wohl behaupten, dass ich den ältesten Beruf der Welt habe." Der Hochbauingenieur unterbrach ihn und sagte, "Aber noch vorher im Buch Genesis heißt es, dass Gott die Ordnung des Himmels und der Erde aus dem Chaos geschaffen hat. Das war der erste und wahrscheinlich der spektakulärste Einsatz des Bauingenieurwesens. Deshalb, lieber Doktor, irren Sie sich : Ich habe den ältesten Beruf der Welt." Der Informatiker lehnte sich in seinem Stuhl zurück, lächelte und sagte dann verbindlich : "Und wer, meine Herren, glauben Sie, hat das Chaos erschaffen ? " Booch „Die Komplexität industriell einsetzbarer Software-Systeme überschreitet die Kapazität der menschlichen Intelligenz.“ 2.4.2 Die Struktur komplexer Systeme Nahezu alle komplexen Systeme nehmen dieselbe kanonische Form an : 205 206 NewClass A1 NewClass2 NewClass13 NewClass5 A2 NewClass4 NewClass3 NewClass7 NewClass8 NewClass9 A3 NewClass10 NewClass6 NewClass14 NewClass15 NewClass11 A4 NewClass12 Bild Kanonische Form komplexer Systeme Komplexe Systeme sind hierarchisch. Die Ebenen der Hierarchie stellen auch unterschiedliche Abstraktionsebenen dar. Auf jeder Abstraktionsebene finden wir eine Ansammlung von Funktionseinheiten, welche zusammenarbeiten, um den höheren Ebenen bestimmte Dienste zur Verfügung zu stellen. Im Prinzip müssen auf alle Teile eines komplexen Systems die Aktivitäten Requirements, Analyse, Design, Implementation und Test angewendet werden. Wie kann man die Fülle von Aktivitäten sinnvoll strukturieren ? 2.4.3 2.4.3.1 Grundlegende Konzepte des RUP Use Case Driven Das Use Case Model beschreibt die komplette Funktionalität des Systems und ersetzt die traditionelle funktionale Spezifikation. 206 207 Das Analyse&Design Model wird ausgehend von den Use Cases erarbeitet und zeigt die Realisierung der Use Cases. Die Implementation wird gegen die Use Cases getestet. Use Cases bilden die Basis der Bedienungsanleitung. Use Cases helfen die Reihenfolge der Releases festzulegen. Jede Release imlementiert eine Menge von Use Cases. 2.4.3.2 Architecture Centric Use Cases und Architektur beeinflussen sich gegenseitig. Die Architektur erwächst zunächst aus einigen Use Cases und ermöglicht dann die Realisierung weiterer oder zukünftiger Use Cases. Damit werden die Use Cases dann auch durch die Architektur beeinflusst. 2.4.3.3 Iterative and Incremental Sinnvollerweise unterteilt man große Softwareprojekte in viele kleine Miniprojekte. Jedes kleine Miniprojekt stellt eine Iteration dar und resultiert in einem Inkrement des Produktes. Die inkrementelle Vorgehensweise besitzt einige Vorteile. Kontrollierte Iterationen reduzieren das Kosten- und Zeitrisiko. Fehler treten am Anfang der Entwicklung auf. Die Architektur kann daher meist noch ohne eine Verzögerung der Auslieferung korrigiet werden. Die Iterationen erlauben eine Anpassung des Produktes an die Anforderungen des Anwenders. Die Anforderungen des Anwenders ändern sich in der Regel durch die Verwendung des Produktes. Alle Beteiligten Personen lernen während des Entwicklungsprozesses. Dies erleichtert insbesondere die Einarbeitung neuer Entwickler. Manche Risiken eines Projektes weden erst sichtbar, wenn erste Releases vorliegen. Die iterative Vorgehensweise beinhaltet hingegen die Gefahr, echte Verbesserungen durch eine Serie nicht konvergenter Änderungen zu ersetzen. Das Softwaresystem wird instabil. Solche Schleifen entstehen auch bereits in der Analyse-, der Entwurfs- oder der Implementationsphase. Die verschiedenen Phasen Analyse, Entwurf sind daher nur schwer zu trennen. 2.4.3.4 Strukturierung des Entwicklungsprozesses 2.4.3.4.1 Phasen-Aktivitäten Diagramm Man strukturiert einerseits die Lebenszeit eines Softwareproduktes in Phasen und andererseits die für das Produkt aufgewendete Arbeit in Aktivitäten. Die Aktivitäten werden in Disciplines gruppiert. 207 208 Bild Phasen-Aktivitäten Diagramm 2.4.3.4.2 Disciplines Business Modeling Requirements Analyse&Design Implementation Test Deployment Configuration&Change Management Project Management Environment (vgl. oben) 2.4.3.4.3 Zyklen Das Leben des Entwicklungsprozesses besteht aus einer Reihe von Zyklen. Jeder Zyklus endet mit der Auslieferung einer neuen Release des Systems an den Kunden. Eine Release umfasst nicht nur den ausführbaren Code sondern alle für eine Wartung und Weiterentwicklung benötigten Model Elemente. 2.4.3.4.4 Phasen Jeder Zyklus wird in die 4 Phasen Inception, Elaboration, Construction, Transition unterteilt. Buch S. 11 Bild 1.5 Buch S. 104 Bild 5.6 Jede Phase kann von dem Manager oder dem Entwickler noch feiner in Iterationen unterteilt werden. Im Prinzip kann jede Iteration als Miniwasserfall betrachtet werden, der alle Aktivitäten von Requirements bis Test durchläuft. Je nach Phase ist jedoch das Ausmaß der einzelnen Aktivitäten unterschiedlich ausgeprägt. So liegt z.B. in der Inception Phase der Schwerpunkt auf der Ermittlung der Anforderungen, in der Elaboration Phase der Schwerpunkt auf Analyse&Design und in der Construction Phase liegt der Schwerpunkt auf Implementation und Test. 208 209 Jede Iteration führt zu einem Inkrement der Model Elemente, die das System repräsentieren. 2.4.3.4.5 Milestones Jede Phase endet mit einem Meilenstein. Ein Meilenstein bildet einen Synchronisationspunkt für parallel laufende Aktivitäten. Er ist definiert durch die Verfügbarkeit eine wohldefinierten Menge von Model Elementen und Dokumenten. Jede kleine Iteration endet mit einem minor Milestone. Diese kleinen Meilensteine dienen zur Reflexion des Zeitplans und des Budgets. Meilensteine ermöglichen z.B., die Entscheidung zu treffen, ob die nächste Phase angegangen wird oder das Projekt abgebrochen wird. Aufzeichnungen über den verwendeten Aufwand innerhalb eines Meilensteins erlauben bessere Schätzungen in zukünftigen Projekten. 2.4.4 2.4.4.1 Phasen des RUP und zugehörige primäre Aktivitäten Inception Das Ziel ist es, entscheiden zu können, ob eine Entwicklung des Produktes lohnt. RUPTool The scope of the project should be understood, and the stakeholders initiating the project should have a good understanding of the project's ROI (return on investment), i.e. what is returned, for what investment cost. Given this knowledge, a go/no go decision can be taken. Die Vision des Endproduktes wird entwickelt. Die Grenzen des Systems werden in Form von Actors identifiziert sowie die Interfaces zu externen Systemen auf einem abstrakten Level beschrieben. Die Interaktion des Systems mit den Aktoren wird in Use Cases (< 10% der Use Case Masse) beschrieben. Das Analysemodel (< 5% der gesamten Analyse) dient zur Strukturierung und präzisen Formulierung der Requirements. Erste Überlegungen zum Kern der Architektur werden angestellt. Der Focus liegt dabei auf neuen, riskanten oder schwierigen Teilen des Systems, die das gesamte Projekt gefährden können. Es geht an dieser Stelle nur darum abzuklären, ob eine Lösung existiert. Wir machen glaubhaft, dass eine Architektur existieren wird. Erste Überlegungen zu Interfaces für Subsysteme, zur Middleware, zur Realisierung kritischer Use Cases, zu Supplementary Requirements, zum Deployment Model. Critical risks werden identifiziert und in einer Risk List festgehalten. Ein (Wegwerf-)Prototyp demonstriert den potentiellen Usern oder Käufern die Vorzüge des neuen Systems. Prototypen dienen auch zur Untersuchung riskanter Systemteile. (Nur bei dringendem Bedarf !) Die erwarteten Aufwände werden sehr grob geschätzt (Zeit, Geld, Personal, Hardware usw.) Der Markt und der Return of Investment wird grob geschätzt. (Genaue Zahlenangaben sind zu diesem Zeitpunkt nicht möglich) 209 210 Die erwarteten Risiken werden identifiziert und priorisiert Ggf. wir ein „Proof of concept“ Prototype erstellt. Kleines Team bestehend aus Project Manager Architect Experienced Developer Test Engineer User Es kann nicht Alles dokumentiert werden. !!! Die an der Inception Phase beteiligten Personen dienen daher auch als Gedächtnis des Teams !!! 2.4.4.2 Elaboration Das Ziel ist es, die Architektur des Systems zu definieren. RUPTool The result of this initial iteration would be a first cut at the architecture, consisting of fairly described architectural views (use-case view, logical view, process view, deployment view, implementation view) and an executable architecture prototype. Alle wichtigen Use Cases (80%) werden spezifiziert. Eine Detailbeschreibung erfolgt für ca. 40% der Use Cases. Aber auch von diesen wird nur ein Bruchteil der Szenarios im Detail durchdacht. Der Problembereich wird analysiert. Das System wird in Packages zerlegt. Die Realsierung der Use Cases wird überlegt. Der Focus liegt auf den architekturrelevanten Use Cases (< 10%). Nicht architekturrelevante Details werden in die Construction Phase verschoben. Subsysteme, von denen man aus Erfahrung weiss, dass sie ohne Probleme designed werden können, werden nicht im Detail durchdacht. Auf diese Weise entsteht ein Skelett des Systems. Ggf. wird eine ausführbare erste Version des Systems zur Verifizierung der Architektur gebildet. Die Architektur wird in mehreren Iterationen entwickelt in Abhängigkeut von der Größe des Systems, den Risiken, der Neuheit, der Komplexität und der Erfahrung der Entwickler. Die Iterationen werden solange fortgesetzt, bis die Architektur stabil wird. Es werden strategische Entscheidungen getroffen bzgl. Frameworks, Middleware (z.B. MFC, ODBC, Corba) Die Risiken werden identifiziert und priorisiert. Ggf. werden ausführbaren Prototypen zur Abschätzung von Risiken entwickelt. Der Projektplan für den Bau des Systems in der Construction Phase wird entwickelt. Der Projektplan identifiziert kontrollierte Serien von Releases. Die Erstellung des Projektplans gelingt oft durch Anordnen der grundlegenden Szenarien. Die Anordnung wird z.B. so sortiert, dass die riskanten oder für den Kunden wichtigen Szenarien zuerst realisiert werden. Es werden Testfälle entwickelt. Es werden Ziele, Termine und Entwicklungsresourcen festgelegt. Die Entwicklungsumgebung für die Construction Phase wird vorbereitet. 210 211 Die Architecture Baseline, die in der Elaboration Phase entwickelt wird, überlebt in Form einer Architecture Description. Diese Beschreibung enthält den für das Verständnis der Architektur relevanten Teil der Model Elemente, Begründungen für strategische Entscheidungen, Performance- oder Speicheranforderungen, verwendete Patterns, kurz all das, was die Entwickler wissen müssen. Nicht enthalten sind z.B. Testfälle. Kleines Team bestehend aus Project Manager Architect Experienced Developer Test Engineer Wiederverwendungsmanager Zukünftige Leiter der Subsysteme User 2.4.4.3 Construction Das Ziel ist die Erstellung einer „beta release“. RUPTool The main result of a late iteration in the construction phase is that more functionality is added, which yields an increasingly more complete system. The results of the current iteration are made visible to developers to form the basis of development for the subsequent iteration. Das Produkt wird bis zur Einführung beim Kunden in einer Serie von Iterationen entwickelt. Jede Iteration implementiert eine Gruppe von Use Cases. Component Engineers designen und implementieren die erforderlichen Subsysteme und Klassen. Sie kompletieren die Requirements zu 100 % Design user interface, Complete use case description Sie vervollständigen die Use Case Realizations. Sie designen Subsysteme und Klassen. Sie implementieren Subsysteme und Klassen. Service subsystems, die für einen use case benötigt werden, werden in der regel aus Effizienz Gründen vollständig implementiert und nicht nur soweit wie es den use case betrifft. Die resultierenden Subsysteme und Klassen werden getestet. Zunächst werden sie einem Unit Test unterzogen und dann dem Integrationstest. Der test engineer definiert test cases and test procedures zur Verifizierung der testbaren Requirements. Er modifiziert test cases und test procedures der vorausgehenden builds für Regressionstests Der System Integrator definiert die Folge der Builds in Form eines Integration Build Plan (meist bottom up). Der System Integrator erzeugt nach dem Build Plan die Builds. Der Integration Tester testet die Builds Dazu führt er die Test Cases gemäß der Test Procedures aus Für manche Klassen werden Stubbs benötigt um ein Build durchführen zu können. Ferner werden driver implementiert um nicht vorhandene components zu simulieren Gefundene Fehler werden gelogged und den Projekt Manager mitgeteilt 211 212 Die component engineers fixen die Fehler. Die test engineer evaluieren die tests. Ggf werden test cases modifiziert oder neue definiert. Der Project manager vergleicht den aktuellen Stand des Projektes mit den Schätzungen aus der Elaboration Phase Ein Maß für den Fortschritt des Projektes ist completion of builds and iterations im Vergleich zum Plan Die gesammelten Erfahrungen werden in die Pläne für die zukünftigen Releases eingearbeitet Viele Entwickler, großes Team. Use Case engineer Component engineer Test engineer System integrator Itegration tester System tester System analyst Architect 2.4.4.4 Transition Das Ziel ist, die Ausliederung an den Kunden. RUPTool The transition phase culminates in the delivery to the customer of a complete system (and ancillary support artifacts) with functionality and performance as specified, and demonstrated in acceptance testing. The customer takes ownership of the software after a successful acceptance test. Z.B. werden ß-Tester selektiert und Test Instruktionen vorbereitet. Es wird ein User Manual oder Tutorial erstellt. Das Produkt wird an die Anwender verteilt. Die Anwender werden geschult. Fehler werden korrigiert. Schwere Fehler führen zu einer delta Release, kleinere werden auf die nächste Release verschoben. Ggf. werden neue kleinere Anforderungen im Rahmen der vorhandenen Architektur realisiert. 3 3.1 Komponentenbasierte Softwareentwicklung am Beispiel von Enterprise Java Beans Literatur Enterprise Java Beans Richard Monson-Haefel O’Reilly 1999 3.2 Introduction Wir betrachten eine E-Commerce Anwendungen im Internet. Eine Bestellung mittels Internet tangiert in der Regel viele verteilte Glieder der Geschäftsprozesskette. So möchte der Anbieter z.B. vom Hersteller Angaben über 212 213 individuelle Produktvarianten und Preise, vom Logistikpartner Auslieferungstermine und von seiner Bank ein Finanzierungsangebot. Zur Automatisierung solcher Geschäftsprozesse, hätte man gerne ein System von flexibel kombinierbaren und damit wiederverwendbaren Softwarekomponenten. Diese Komponenten sollten ihre Daten darüberhinaus selbständig in eine Datenbank legen, Transaktionen beachten und auf verschiedenen Rechnern laufen können. Enterprise JavaBeans (EJB) definiert ein standard distributed component model für die Entwicklung skalierbarer und portierbarer business systems. Das Komponentenmodell erlaubt die Verteilung von business objects in Form von beans (Bohnen). Ein EJB Server ist für object brokering, transaction management, security, persistence und concurrency verantwortlich. Die Kapselung von Daten einer Datenbank in business object beans hat Vorteile gegenüber einem direkten Zugriff auf die Datenbank. Der Aufruf einer Methode eines objects ist in der Regel einfacher als die Ausführung eines SQL Statements. Die Objektifizierung von Daten mittels beans erleichtert die Wiederverwendung. Die Datenbankimplementierung kann modifiziert werden, ohne dass der Client etwas davon bemerkt. Attribute der bean, z.B. Transaktionsverhalten, Persistenz und Sicherheit können über Properties ohne Code Modifikationen eingestellt werden. Eine enterprise bean ist nicht nur betriebssystemunabhängig sondern auch unabhängig von der Implemenation des application servers sofern dieser die EJB Spezifiaktion implementiert. 3.2.1 Distributed Object Architectures 3.2.1.1 Three Tier Architecture Distributed business objects bilden in der Regel die mittlere Schicht einer drei Schichten Architektur, bestehend aus GUI, business logic und einer Datenbank. // Einfügen Bild 3 Schichten Architektur Distributed object technologie (Java EJB, CORBA, Microsoft DCOM) erlaubt die Verwendung der Objekte durch client applications auf einem anderen Computer. Wie funktiononiert das ? 3.2.1.2 Stub und Skeleton // Einfügen Bild Stub Skeleton Das Prinzip besteht darin, ein Stellvertreter-Objekt auf dem lokalen Computer so aussehen zu lassen wie das Orginal auf einem entfernten Computer. Die Archietktur besteht aus dem objectServer, skeleton (Skelett) und stub (Stummel). Jeder objectServer hat eine zugehörige stub- und skeleton-Klasse. Stub und skeleton sind dafür verantwortlich, den objectServer so aussehen zu lassen, als würde er auf der lokalen client Maschine laufen. Dies wird erreicht durch ein remote method invocation (RMI) Protokoll. Java, Corba, Microsoft DCOM verwenden unterschiedliche RMI Protokolle. // Einfügen 213 214 3.2.1.3 Interface und Server Technologie am vereinfachten Modell Beispiel PersonModel public interface Person { public int getAge() throws Throwable; public String getName() throws Throwable; } public class PersonServer implements Person { int age; String name; public PersonServer(String name, int age){ this.age = age; this.name = name; } public int getAge(){ System.out.println("getAge() called !"); return age++; } public String getName(){ System.out.println("getName() called !"); return name; } } public class PersonClient { public static void main(String [] args){ try{ Person person = new Person_Stub(); int age = person.getAge(); String name = person.getName(); System.out.println(name+" is "+age+" years old"); age = person.getAge(); name = person.getName(); System.out.println(name+" is "+age+" years old"); age = person.getAge(); name = person.getName(); System.out.println(name+" is "+age+" years old"); }catch(Throwable t) {t.printStackTrace();} } } public class Person_Stub implements Person { Socket socket; public Person_Stub()throws Throwable{ } public int getAge( ) throws Throwable{ // when this method is invoked, stream the method name to the // skeleton // create a network connection to the skeleton. // Replace "myhost" with your own IP Address of your computer. socket = new Socket("Edelmann",9000); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("age"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return inStream.readInt(); } public String getName()throws Throwable{ // when this method is invoked, stream the method name to the // skeleton // create a network connection to the skeleton. // Replace "myhost" with your own IP Address of your computer. socket = new Socket("Edelmann",9000); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("name"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return (String)inStream.readObject(); } } 214 215 public class Person_Skeleton extends Thread{ PersonServer myServer; public Person_Skeleton(PersonServer server){ // get a reference to the object server that this skeleton wraps this.myServer = server; } public void run(){ try{ // create a server socket on port 9000 ServerSocket serverSocket = new ServerSocket(9000); // wait for and obtain a socket connection from stub boolean finished = false; while (!finished) { Socket socket = serverSocket.accept(); if(socket != null) { // create an input stream to receive requests from stub ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); // Read next method request from stub. Block until request is // sent. String method = (String)inStream.readObject(); // Evalute the type of method requested if(method.equals("age")){ // invoke business method on server object int age = myServer.getAge(); // create an output stream to send return values back to // stub. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // send results back to stub outStream.writeInt(age); outStream.flush(); }else if(method.equals("name")){ // invoke business method on server object String name = myServer.getName(); // create an output stream to send return values back to // the stub. ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // send results back to stub outStream.writeObject(name); outStream.flush(); } } } }catch(Throwable t) {t.printStackTrace();System.exit(0); } } public static void main(String args [] ){ // obtain a unique instance Person PersonServer person = new PersonServer("Richard", 34); Person_Skeleton skel = new Person_Skeleton(person); skel.start(); } } Der stub implementiert ein interface mit denselben business-methods wie das serverObject. Jedoch enthalten die Methoden des stub keine business-logic. Stattdessen enthalten sie die Anweisungen, die erforderlich sind, um Anforderungen an den skeleton zu senden und die Ergebnisse zu empfangen. Wenn ein Client eine Methode des stub aufruft, werden Name und Parameter als stream zum skeleton übertragen, dieser parst den stream und ruft die korrespondierende Methode des objectServers. Jeder Wert, den die gerufene Methode zurückliefert, wird als stream zum stub zurücktransportiert. Der stub liefert dann den Wert an den Client als ob die business logic lokal ausgeführt worden wäre. Wirkliche distributed object protocols wie CORBA, DCOM, Java RMI generieren stub und skeleton und unterstützen exception handling. Sogenannte object request broker (ORB) erlauben die Verwaltung von serverObjects. Sie unterstützen transaction management ind security. 3.2.2 Server-Side Components (Selbststudium) 215 216 Die Einkapselung von business logic in business objects hat in jünster Zeit eine große Bedeutung in der IT Industrie erhalten. Wenn die Software, welche das business modelliert, in business objects gekapselt werden kann, wird sie flexibel, erweiterungsfähig und wiederverwendbar. Ein server-side component model definiert eine Architektur für die Entwicklung verteilter business objects. In diesem Sinne ist ein business System eine Ansammlung von server-side components wie customer, product, reservation, warehouse usw. Products können in dem warehouse gespeichert werden oder an den customer ausgeliefert werden. Ein customer kann ein product reservieren oder kaufen. Ein solches System ist flexibel, da es objektifiziert ist, und gut zugreifbar, weil es aus verteilten Objekten besteht. Component Models JavaBeans (package java.beans) ist ein Komponentenmodell für intraprocess Zwecke, EJB (package javax.ejb) ist ein Komponentenmodell für interprocess Zwecke. JavaBeans kann etwa verwendet werden, um eine GUI zusammenzusetzen. Eine Komponente ist etwa ein PushButton. EJB wird hingegen verwendet, um eine verteilte business logic aufzubauen. Eine Komponente ist etwa ein customer object. 3.2.3 Component Transaction Monitors (Selbststudium) Sogenannte application server managen die Komponenten zur Laufzeit und machen sie für clients verfügbar. Ein application server besteht in der Regel aus verschiedenen Technologien. TP Transaction processing monitors Transaction processing monitors sind operating systems für business systems. Sie managen Speicher, Datenbankzugriff und Transaktionen. Die business logic wird dabei durch prozedurale Applikationen gebildet. Der Zugriff erfolgt meist mittels remote procedure calls (RPC). TP monitors sind nicht objektorientiert. ORB object request broker Der ORB ist vergleichbar mit einer Telefonzentrale. Seine wesentliche Aufgabe besteht in der Übermittlung von Operationsaufrufen und ihren Ergebnissen zwischen Client- und Server-Objekten. Der ORB benutzt dazu die Schnittstellen der Client und Server Objekte. Die Übertragung basiert z.B. auf dem IIOP (Internet Inter ORB Protokoll). Dieses wiederum verwendet TCP/IP. Clients erhalten Dienstleistungen über Anforderungen (Request). Ein Request umfasst die Angabe des Servers, der Operation, der Ein- und Ausgabeparameter, optionaler Ausnahmebedingungen und optionaler Kontextangaben. Für den Client ist nicht mehr erkennbar, welcher Prozess auf welchem Rechner seine Anforderung erfüllt. Die Verlagerung von Diensten auf einen anderen Netzknoten oder eine Änderung der Netztopologie hat also keine Auswirkungen auf den Client. Die Technologien für verteilte Sytseme wie etwa CORBA oder Java RMI sind aus der RPC Technologie erwachsen. Der signifikante Unterschied besteht darin, dass man eine Methode eines Objektes aufruft und nicht eine Funktion einer Applikation. Die verteilten Objekte werden gewöhnlich von einem ORB verwaltet. ORB’s haben sich jedoch in Umgebungen mit einem hohen Volumen von Transaktionen als unbrauchbar erwiesen. Darüberhinaus überlassen sie die Behandlung von Transaktionen, Persistenz und concurrency der Applikation. CTM component transaction monitor Aus der traditionellen transaction Technologie und der ORB Technologie entwickelte sich (1999) der component transaction monitor (CTM) als hybrid model. CTM`s managen concurrency, transactions, object distribution, load balancing, security, persistence und resource management. Entwickler müssen diese Möglichkeiten nicht implementieren. 216 217 CTM`s werden von der relational database Industrie, der application server Industrie, der web server Industrie und der CORBA ORB Industrie hergestellt. Die Beziehung des CTM zu seinen components ist diesebe wie die Beziehung des Eisenbahnsystems zu seinen Zügen. Das Eisenbahnsystem kümmert sich um load balancing, concurrency und resourcen management. Das Eisenbahnsystem liefert die Infrastruktur für die Züge. Der Lokführer kümmert sich um den Transport und nicht um die Infrastruktur. Verschiedene Eisenbahnsysteme haben etwa unterschiedliche Spurbreiten. Die Züge des einen Systems laufen nicht auf den Schienen des anderen Systems. Der MTS ist Microsofts CTM Der Microsoft Transaction Server (MTS) wurde 1996 eingeführt. Der Service für verteilte Objekte basiert auf der DCOM Technologie. Ein verwaltetes business object muss COM konform sein. Die Verwendung von MTS erzwingt die Microsoft Plattform. MTS unterstützt keine persistenten Komponenten. CORBA Common Object Request Broker Architecture CTM`s Die meisten nicht Microsoft CTM`s basieren auf dem offenen CORBA Standard als Technologie für verteilte Objekte. Systemanbieter und Anwender objektorientierter Techniken haben sich 1989 zur OMG (Object Management Group) zusammengeschlossen, um Standards und Spezifikationen für eine Infrastruktur zu entwickeln, die für verteilte objektorientierte Anwendungen erforderlich ist. Diese Spezifikationen wurden zum de-facto-Standard in der Softwareindustrie. (Z.Zt., d.h. 1999 ca 800 Firmen) Die OMG arbeitet lediglich die Spezifikation aus. Sie überläßt die Erstellung der Software kommerziellen Anbietern. Neben dem CORBA Standard benötigte man aber ein gemeinsames component model. EJB Enterprise JavaBeans ist Sun’s CTM Enterprise JavaBeans is a standard server-side component model for component transaction monitors. 19 97 gab sun die erste Spezifikation von Enterprise JavaBeans heraus. Daher kann man nun erwarten, dass business objects, die dem EJB Standard enstprechen, in jedem CTM arbeiten, welches die EJB Spezifikation unterstützt. IBM`s Sun Francisco business object framework wird sich dem EJB component model anpassen. 3.3 Architectural Overview Man unterscheidet entity beans und session beans. 3.3.1 Beispiel CruiseBean import javax.ejb.EntityContext; import javax.ejb.FinderException; public class CruiseBean implements javax.ejb.EntityBean { public String cruiseId; public String cruiseName; public String cruiseShip; public String cruiseDate; public int cruiseDays; public int cruiseMaxPerson; public String ejbCreate(String cruiseId, String cruiseName, cruiseShip,String cruiseDate, int cruiseDays, int cruiseMaxPerson) { this.cruiseId = cruiseId; this.cruiseName = cruiseName; this.cruiseShip = cruiseShip; this.cruiseDate=cruiseDate; this.cruiseDays=cruiseDays; this.cruiseMaxPerson=cruiseMaxPerson; return null; String 217 218 } public String getCruiseId() { return cruiseId; } public String getName() { return cruiseName; } public void setName(String cruiseName) { this.cruiseName = cruiseName; } public void setShip(String cruiseShip) { this.cruiseShip = cruiseShip; } public String getShip() { return cruiseShip; } public void setDate(String cruiseDate) { this.cruiseDate = cruiseDate; } public String getDate() { return cruiseDate; } public int getMaxPerson() { return cruiseMaxPerson; } public void setMaxPerson(int cruiseMaxPerson) { this.cruiseMaxPerson = cruiseMaxPerson; } public int getDays() { return cruiseDays; } public void setDays(int cruiseDays) { this.cruiseDays = cruiseDays; } public void ejbPostCreate(String cruiseId, String cruiseName, cruiseShip,String cruiseDate, int cruiseDays, int cruiseMaxPerson){} public void setEntityContext(EntityContext ctx){} public void unsetEntityContext(){} public void ejbActivate(){} public void ejbPassivate(){} public void ejbLoad(){} public void ejbStore(){} public void ejbRemove(){} } String EJB erfordert die Verwendung der Java RMI Konventionen. 3.3.2 Entity beans // Einfügen Mit anderen Worten modellieren entity beans real world objects. Diese objects entsprechen gewöhnlich persistenten records in einer Datenbank. Im Beispiel eines Reisebüros benötigen wir entities welche etwa customer, cruise, cabins und reservations repräsentieren. Entity beans modellieren Daten und Verhalten. Das Verhalten betrifft im Wesentlichen business logic, die sich direkt auf die Daten bezieht. Entity beans repräsentieren business concepts, die als Nomen ausgedrückt werden können. 3.3.3 Beispiel TravelAgentBean import javax.naming.InitialContext; import javax.ejb.SessionBean; 218 219 import import import import import import import javax.ejb.SessionContext; javax.ejb.DuplicateKeyException; javax.ejb.CreateException; javax.rmi.PortableRemoteObject; java.rmi.RemoteException; java.util.*; Reservation; public class TravelAgentBean implements SessionBean { ReservationHome reservationHome; // Achtung : Kann fuer stateless session beans kritisch werden Enumeration reservationListe; // Achtung : Kann fuer stateless session beans kritisch werden public Reservation String customerId) travelAgentReservation(String throws reservationId, DuplicateKeyException, String cruiseId, CreateException, DataExistsException { Reservation theReservation = null; //String idReservation = reservationId; try { InitialContext context = new InitialContext(); Object objectReservationHomeRef = context.lookup("reservation"); reservationHome (ReservationHome)PortableRemoteObject.narrow(objectReservationHomeRef, ReservationHome.class); } catch (Exception NamingException) { NamingException.printStackTrace(); } = // Pruefe, ob bereits Reservierung fuer den Kunden mit der vorgegebenen Kreuzfahrt existiert boolean found = false; try { reservationListe = reservationHome.findAll(); while(reservationListe.hasMoreElements()) { theReservation = (Reservation)reservationListe.nextElement(); if(cruiseId.equals(theReservation.getCruiseId()) && customerId.equals(theReservation.getCustomerId())) { found = true; return null; //throw new DataExistsException(); //noch nicht funktioniert // break; } } } catch (Exception ex) { ex.printStackTrace(); } try{ if(found == false) //theReservation = reservationHome.create(idReservation, cruiseId, customerId); theReservation = reservationHome.create(reservationId, cruiseId, customerId); } catch (Exception e) { String message = e.getMessage(); e.printStackTrace(); } return theReservation; } public Reservation getRecordReservation(String reservationId)//idReservation) { Reservation record = null; try { record = reservationHome.findByPrimaryKey(reservationId);//idReservation); } catch (java.rmi.RemoteException e) { String message = e.getMessage(); } 219 220 catch (javax.ejb.FinderException e) { e.printStackTrace(); } return record; } public public public public public public public void void void void void void void ejbCreate() { } setSessionContext(SessionContext context) { } ejbRemove() { } ejbActivate() { } ejbPassivate() { } ejbLoad() { } ejbStore() { } } 3.3.4 Session beans // Einfügen Session beans agieren als Agenten für den client. Session beans managen die Interaktionen zwischen den entity beans. Session beans sind der angemessene Platz für die business logic. Sie steuern unter Verwendung von entity beans den workflow. Sie repräsentieren daher den workflow. Der workflow beschreibt alle Schritte, die getan werden müssen, um eine bestimmte Arbeit zu erledigen. Einige Aktionen kann man durch einen direkten Aufruf einer Methode der cruise bean erledigen. Jedoch weiß die cruise bean nichts über den context einer Aktion. Bucht man also etwa einen Passagier für ein Kreuzfahrtschiff, so muss man sicher eine cruise bean verwenden. Man muss jedoch eine Menge Wissen verwenden, welches nichts mit einem Schiff zu tun hat. Z.B. muss man etwas über den Passagier wissen, über den Ticketpreis, über den Fahrplan usw. Eine TravelAgent session bean wird eine cruise, eine customer und eine reservation bean verwenden, um eine Reservierung durchzuführen. Die TravelAgent session bean repräsentiert in diesem Falle nicht die Menge der Kunden, Kreuzfahrten und Kabinen. Sie kann daher nicht als Entität aufgefasst werden. Sie repräsentiert lediglich das Wissen um Beziehungen, Vorschriften, Randbedingungen im Zusammenhang mit der Buchung. Sie repräsentiert das Wissen eines Travelagent aber nicht die Person. Die komplexe Steuerung einer Buchung sollte nicht der GUI und damit im Verwaltungsmodell der Sekretärin obliegen. Die Buchung sollte aber auch nicht von dem Bankkonto oder dem Schiff vorgenommen werden. Das Bankkonto sollte nichts von Kreuzfahrten wissen. Die Kreuzfahrt bean sollte nichts von Bankkonten wissen. Daher benötigt man für diese Aufgabe ein eigenes Objekt. Die Aktivität, welche durch die session bean repräsentiert wird, ist in der Regel transient. Sie repräsentiert keinen Record der Datenbank. Jedoch erzeugt die session bean z.B. eine Reservierung, die einem customer eine ganz spezielle cabin auf einer ganz speziellen cruise zuweist. Diese Aktionen führen zu Änderungen der entity beans und diese Änderungen werden durch die Datenbank reflektiert. Client applications verwenden die cruise bean oder die customer bean in unterschiedlicher Weise. Manche Verwendungen sind vorhersagbar, die meisten nicht. Darüberhinaus ändert sich die Verwendung mit der Zeit. Um nicht die cruise oder customer bean permanent ändern und anpassen zu müssen, ist es wichtig, die Daten von dem workflow zu trennen. Die wesentliche Unterscheidung zur entity bean besteht darin, dass eine entity bean einen persistenten Status hat, wohingegen eine session bean Interaktionen modelliert und keinen persistenten Status hat. 220 221 Wenn man an ein Theaterstück denkt, repräsentieren die entity beans die Schauspieler und die session bean das Manuskript. Die Verschiebung der business logic in session beans führt zu thin clients und reduziert den Netzverkehr. Ein Request des Client führt zu einem Methodenaufruf der session bean und vielen Methodenaufrufen für weitere session und entity beans, die in der Regel ebenfalls auf dem server liegen. So wird das Netz nur durch einen Methodenaufruf belastet. Ferner benötigt der Client nur eine einzige Netzverbindung zu der einen session bean, z.B. dem TravelAgent. 3.3.5 The bean container contract Enterprise beans kommunizieren mit dem EJBServer über ein wohldefiniertes Komponentenmodell. Die Basis dieses Komponentenmodels bilden die EntityBean und SessionBean interfaces. Die entityBean Schnittstelle umfasst etwa solche Methoden wie ejbCreate() ejbPostCreate() ejbActivate() ejbLoad() ejbStore() Sie dienen dazu, die bean durch den EJB Server über Events zu informieren, die seinen Lebenszyklus betreffen. Wenn der Zustand der bean keine Reaktion auf die Events erfordert, können die Methoden leer implementiert werden. EJBContext ist ein interface, welches von dem container implementiert wird. EntityBeans verwenden die abgeleitete Klasse EntityContext, session beans die abgeleitete Klasse SessionContext. EJBContext beliefert die bean mit Informationen über die umgebende Welt. 3.3.6 Beispiel Cruise Interface and Class CruiseHome import java.rmi.RemoteException; public interface Cruise extends javax.ejb.EJBObject { public String getName( )throws RemoteException; public void setName(String name)throws RemoteException; public String getShip( )throws RemoteException; public String getCruiseId( )throws RemoteException; public void setShip(String ship)throws RemoteException; public String getDate( )throws RemoteException; public void setDate(String termin)throws RemoteException; public int getMaxPerson()throws RemoteException; public void setMaxPerson(int maxPers)throws RemoteException; public int getDays()throws RemoteException; public void setDays(int dauer)throws RemoteException; } import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; import java.util.Enumeration; public interface CruiseHome extends javax.ejb.EJBHome { public Cruise create(String cruiseId, String cruiseName,String cruiseShip,String cruiseDate, int cruiseDays,int cruiseMaxPerson) throws RemoteException, javax.ejb.CreateException; public Cruise findByPrimaryKey(String cruiseId) throws RemoteException, javax.ejb.FinderException; public Enumeration findAll() throws FinderException,RemoteException; } 3.3.7 Classes and Interfaces 221 222 Um eine enterprise bean zu implementieren, muss man zwei Interfaces und zwei Klassen implementieren. Remote interface Dieses definiert die business methods des bean. Home interface Dieses definiert die life cycle methods : Erzeugen, Löschen und Finden einer bean. Bean class Diese Klasse implementiert die business methods. Primary key Diese Klasse definiert den primary key. Er wird verwendet, um eine spezifische bean zu finden. Die Klasse liefert einen Pointer in die Datenbank und implementiert Serializable(). Basierend auf den Interfaces und der bean class wird eine Menge code generiert. Dieser code erzeugt z.B. die beans, speichert sie in der Datenbank, überträgt die Anforderungen ins Netz usw. Mit diesem generierten code werden die beiden interfaces imlementiert. 3.3.8 Implementation des remote interface // Einfügen Bild EJBObject Auf der Client Seite wird das remote interface duch den stub implementiert. Auf der Server Seite verpackt das sogenannte EJBObject die erzeugte bean class, im Beispiel die CruiseBean und erweitert deren Funktionalität. Dieses EJBObject wird generiert und basiert auf der bean class und den Informationen des sogenannten DeploymentDescriptors. Das EJBObject arbeitet mit dem Container zusammen. Wenn ein client eine Methode des stubs aufruft, empfängt das EJBObject die Anforderung und delegiert sie an die bean instance. Das EJBObject kümmert sich dabei um transactions, security, concurrency. Es existieren verschiedene vendorspezifische Strategien für die Implementierung des EJBObjects 222 223 EJBObject EntityBean EJBObject CruiseBean Cruise EntityBean Cruise CruiseEJB Object CruiseBean CruiseEJBObject a) b) Bild Implementation EJBObject Sämtliche remote interfaces sind von EJBObject abgeleitet. Der Client erhält vom home interface eine Referenz auf ein EJBObject. GetEJBHome() ermöglicht dem Anwender, wieder eine Referenz auf das home interface zu erhalten, wenn das home interface nicht mehr direkt erreichbar ist. GetPrimaryKey() ist für session beans bedeutungslos. Der primary key kann mittels serialization in ein file geschrieben werden. Er kann verwendet werden um assoziations zwischen entity beans zu verwalten. EJBObject.isIdentical() liefert true, wenn zwei Referenzen dieselbe bean repräsentieren. Object.equals() leistet dies nicht, da diese Methode lediglich die beiden stubs vergleicht. Diese können verschieden sein, obwohl sie dasselbe bean object referenzieren. Stateless session beans vom selben Typ werden als gleich angesehen. Für entity beans invalidiert die remove() Methode die remote reference und löscht die Daten aus der Datenbank. Session beans werden freigegeben. 3.3.9 Implementation des home interface Das home interface liefert life-cycle operations und metadata. cruiseHome erweitert EJBHome um spezifiche create() und find() Methoden. Die Klasse unterstützt den Container bei der Verwaltung des bean life cycle (vgl. weiter hinten). Sie ist verantwortlich für die Erzeugung und Vernichtung von beans. Wenn z.B. eine create() Methode des home interfaces gerufen wird, erzeugt EJBHome eine Instanz des EJBObjects und koppelt eine Instanz der CruiseBean daran. Anschließend wird die Methode ejbCreate() der CruiseBean gerufen. find() Methoden für session beans sind bedeutungslos. Sie repräsentieren keine Daten, daher gibt es nichts zu suchen. Die find() Methoden werden im Falle von container managed entities vom EJBServer automatisch generiert. Im Falle von bean managed entities wird jeder Aufruf an eine korrespondierende Methode der bean instance delegiert. 223 224 Mittels JNDI (vgl. weiter hinten) erhält man eine remote reference in Form eines stubs zu dem EJB Home object, welches das home interface auf der Server Seite implementiert. EJBHome.remove() Das Entfernen einer entity bean mittels remove() hat das Löschen der Daten in der Datenbank zur Folge. EJBHome.getEJBMetaData() Diese Funktion liefert eine Instanz von EJBMetaData. Die Klassen EJBObject und EJBHome werden beim Einfügen einer bean in einen Container basierend auf den Klassen des JAR Files generiert. 3.3.10 Beispiel deployment descriptor Ejb-jar.xml <?xml version="1.0" encoding="Cp1252"?> <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 'http://java.sun.com/j2ee/dtds/ejbjar_1_1.dtd'> <ejb-jar> <description>no description</description> <display-name>Ejb1</display-name> <enterprise-beans> <entity> <description>no description</description> <display-name>CruiseBean</display-name> <ejb-name>Cruise</ejb-name> <home>pCruise.CruiseHome</home> <remote>pCruise.Cruise</remote> <ejb-class>pCruise.CruiseBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.String</prim-key-class> <reentrant>False</reentrant> <cmp-field> <description>no description</description> <field-name>cruiseName</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>cruiseDate</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>cruiseShip</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>cruiseDays</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>cruiseId</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>cruiseMaxPerson</field-name> </cmp-field> <primkey-field>cruiseId</primkey-field> </entity> <entity> <description>no description</description> <display-name>ReservationBean</display-name> <ejb-name>Reservation</ejb-name> <home>pCruise.ReservationHome</home> <remote>pCruise.Reservation</remote> <ejb-class>pCruise.ReservationBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.String</prim-key-class> <reentrant>False</reentrant> <cmp-field> <description>no description</description> <field-name>reservationId</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>customerId</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>cruiseId</field-name> </cmp-field> <primkey-field>reservationId</primkey-field> </entity> <entity> <description>no description</description> <display-name>CustomerBean</display-name> <ejb-name>Customer</ejb-name> <home>pCruise.CustomerHome</home> <remote>pCruise.Customer</remote> <ejb-class>pCruise.CustomerBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.String</prim-key-class> <reentrant>False</reentrant> <cmp-field> 224 225 <description>no description</description> <field-name>customerName</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>customerFirstname</field-name> </cmp-field> <cmp-field> <description>no description</description> <field-name>customerId</field-name> </cmp-field> <primkey-field>customerId</primkey-field> </entity> <session> <description>no description</description> <display-name>TravelAgentBean</display-name> <ejb-name>TravelAgent</ejb-name> <home>pCruise.TravelAgentHome</home> <remote>pCruise.TravelAgent</remote> <ejb-class>pCruise.TravelAgentBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Home</method-intf> <method-name>findAll</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>setMaxPerson</method-name> <method-params> <method-param>int</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Home</method-intf> <method-name>create</method-name> <method-params> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> <method-param>int</method-param> <method-param>int</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>getDays</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>getMaxPerson</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>getName</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Home</method-intf> <method-name>findByPrimaryKey</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>getShip</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> 225 226 <method-name>getDate</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>setName</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>remove</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>setShip</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>setDays</method-name> <method-params> <method-param>int</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>setDate</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Cruise</ejb-name> <method-intf>Remote</method-intf> <method-name>getCruiseId</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Reservation</ejb-name> <method-intf>Home</method-intf> <method-name>findAll</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Reservation</ejb-name> <method-intf>Remote</method-intf> <method-name>getReservationId</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Reservation</ejb-name> <method-intf>Home</method-intf> <method-name>create</method-name> <method-params> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Reservation</ejb-name> <method-intf>Remote</method-intf> <method-name>getCustomerId</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Reservation</ejb-name> <method-intf>Home</method-intf> <method-name>findByPrimaryKey</method-name> 226 227 <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Reservation</ejb-name> <method-intf>Remote</method-intf> <method-name>getCruiseId</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Customer</ejb-name> <method-intf>Remote</method-intf> <method-name>setName</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Customer</ejb-name> <method-intf>Remote</method-intf> <method-name>getFirstname</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Customer</ejb-name> <method-intf>Remote</method-intf> <method-name>setFirstname</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Customer</ejb-name> <method-intf>Remote</method-intf> <method-name>getName</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Customer</ejb-name> <method-intf>Home</method-intf> <method-name>create</method-name> <method-params> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>Customer</ejb-name> <method-intf>Home</method-intf> <method-name>findByPrimaryKey</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>TravelAgent</ejb-name> <method-intf>Remote</method-intf> <method-name>getRecordReservation</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>TravelAgent</ejb-name> <method-intf>Remote</method-intf> <method-name>travelAgentReservation</method-name> <method-params> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> <method-param>java.lang.String</method-param> </method-params> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar> 3.3.11 Deployment descriptors and JAR files 227 228 Deployment descriptors sind Klassen, die ähnlich Property files die Anpassung des Verhaltens (SessionBean or EntityBean, security, transactions) einer enterprise bean erlauben, ohne die bean class oder die interfaces ändern zu müssen. Für jede bean wird ein deployment descriptor erzeugt und mit den Daten der bean gefüllt. In der Regel setzt der Anwender die Attribute der deployment descriptor class im Rahmen des Integrated Development Environment (IDE) oder des Deployment Tools mittels Property Sheet. Das deployment descriptor object wird dann serialisiert. Der deployment descriptor kann von Anwendern der bean modifiziert werden. Das deployment descriptor file ist Bestandteil des JAR Files der bean. Das JAR File enthält die bean class, remote interface, home interface, primary key, manifest, serialiserter deployment descriptor. JAR`s manifest ist das Inhaltsverzeichnis des JAR Files. Das Element <!DOCTYPE> beschreibt unter anderem die zugehörige DTD Datei. Diese wird zur Überprüfung der Struktur des Dokumentes verwendet. Die übrigen Elemente sind inneinander verschachtelt und jeweils durch ein Start- und EndeTag begrenzt. Alle Elemente müssen in <ejb-jar> enthalten sein. Alle beans sind im Element <enterprise-beans> enthalten. In einem <entity> Element definierte beans sind entity beans. In einem <session> Element definierte beans sind session beans. Die <cmp-field> führen alle container managed fields in der entity bean Klasse auf. Diese Felder werden in der Datenbank gespeichert und vom container zur Laufzeit verwaltet. Das <assembly-descriptor> Element beschreibt die Sicherheitsrollen und Transaktionsattribute der beans. Das <container-transaction> Element beschreibt, dass Transaktionen „required“ werden. 3.4 Example Cruise nur elektronisch 3.5 Container and Bean Managed Persistence of Entity Beans 3.5.1 Container Managed Persistence Der container weiß, wie die Attribute der bean auf die Felder der Datenbank abgebildet werden und kümmert sich um das Einfügen, Updaten und Löschen. Die bean kann unabhängig von der Datenbank formuliert werden. Dies erhöht die Wiederverwendbarkeit. 3.5.2 Bean Managed Persistence Der Entwickler der bean muss den Code zur Manipulation der Daten in der Datenbank schreiben. Er erhält auf diese Weise eine größere Flexibilität. Jedoch bindet ihn diese Vorgehensweise in stärkerem Maße an die spezifische Datenbank. 3.6 Stateful and stateless session beans 3.6.1 Stateful session beans Eine stateful session bean verwaltet einen Status in Bezug auf den client. Z.B. könnte man bei der TravelAgent bean zunächst mittels setCustomerId(), setCruiseId() remote Referenzen zu Customer und Cruise beans erzeugt. Der nachfolgende parameterlose createReservation() Aufruf funktioniert dann nur, wenn die erforderlichen Referenzen vorher gesetzt wurden. Nach dem Setzen der Customer und Cruise Referenzen befindet sich der TravelAgent daher in einem anderen Status. Dieser Status wird als conversational state bezeichnet, da er die Conversation zwischen bean und client repräsentiert. 228 229 Bei einer stateful session bean ändert sich der Zustand der bean im Verlaufe der Kommunikation mit dem Client. Damit sind die Methodenaufrufe nicht mehr voneinander unabhängig. Stateful session beans können nicht parallel mit verschiedenen clients kommunizieren. Eine stateful session bean ist die Erweiterung der client application. Sie führt Aufgaben im Auftrag des client aus. Sie arbeitet als Agent des client. Jeder Methodenaufruf des client wird daher im Prinzip von derselben instance der bean bedient. Stateful session beans kapseln den conversational state und die business logic auf dem server. Die Auslagerung der business logic auf den server führt zu einem thin client und erleichtert die Verwaltung des Systems. Die Verwaltung des workflow im Auftrag des client führt zu einem interface, welches z.B. direkte Datenbankzugriffe vor dem client verbirgt. Stateful session beans sind nicht persistent. Sie sind während der gesamten Lebensdauer einem einzigen client zugeordnet und haben eine vorgegebene timeout Zeit. Bei jedem Aufruf einer business Methode wird der timeout zurückgesetzt. Wenn der client die bean vor dem Ablauf des timeout nicht mehr verwendet, wird die instance zerstört und die remote reference ungültig. Der client kann die session bean natürlich auch mittels remove() explizit zerstören. Stateful session beans können nicht konkurrierend verwendet werden. Im Falle einer entity bean werden alle client Anforderungen durch dasselbe EJB object koordiniert. Im Falle von session beans ist das EJB object genau einem client zugeordnet. 3.6.2 Stateless session beans Stateless session beans verwalten keine eigenen conversational states (vgl. aktuelle TravelAgent Implementierung). Die Methoden sind völlig unabhängig voneinander. Wenige stateless session beans können daher tausende von Clients parallel bedienen. Viele EJB objects könne wenige Instanzen von stateless session beans nutzen. Stateless session beans könne frei zwischen verschiedenen EJB object geswapped (vgl. weiter hinten) werden. Die beans können sich von einem Aufruf einer Methode zum nächsten keine Informationen merken. Jedoch bedeutet dies nicht, dass die beans keine Attribute haben dürfen. Eine stateless bean kann z.B. eine Referenz auf ein Debugfile halten. Der Ablauf der Methoden darf jedoch nicht von dem Wert der internen Attribute abhängen. Die internen Attribute sind für den Client nicht sichtbar. Der client kann nicht davon ausgehen, beim nächsten Aufruf von derselben bean bedient zu werden. Die Eigenschaft stateless wird über eine Property der SessionDescriptor Klasse definiert. 3.7 Resource Management 3.7.1 Problem EJBServer müssen so konstruiert sein, dass sie ggf. Millionen von beans verwalten können. 3.7.2 Pooling Eine Technik zur Verbesserung der Performance besteht darin, einen pool der benötigten Instanzen der beans anzulegen. Clients werden dann aus diesem Pool bedient. Es ist sehr viel weniger aufwendig, Instanzen aus diesem Pool wiederzuverwenden als permanent neue Instanzen zu erzeugen und wieder zu löschen. Ein EJBServer verwaltet instance pools für jeden Typ von bean. Da die Clients ohnehin nie direkt auf die beans zugreifen können sondern nur über wrapper EJB objects, besteht keine Notwendigkeit, für jeden der Millionen Clients eine bean im Speicher bereit zu halten. Der EJBServer hält in seinem Pool stattdessen eine viel kleinere Zahl von beans zu Erledigung der von den Clients anfallenden Aufträge. 229 230 Die Größe des pools kann darüberhinaus dem aktuellen Bedarf angepasst werden. 3.7.3 Swapping Während Pausen können bean instances an den pool zurückgegeben werden. Man spricht in diesem Falle von instance swapping. Im Falle von stateless session beans bereitet es keine Probleme, wenn ein Client zwischen zwei Methodenaufrufen eine andere bean instance zugewiesen bekommt. Bild Swapping Entity beans partizipieren ebenfalls an dem Mechanismus des instance swapping. Sie haben keinen conversational state, der zwischen zwei Aufrufen gemerkt werden muss. Ihr Status in Form der Daten ist direkt in der Datenbank gespeichert. 3.7.4 3.7.4.1 The Life Cycle of an Entity Bean States EJB definiert die states der entity bean über die Beziehung zum instance pool. // Einfügen 230 231 Bild Zustände der bean 3.7.4.2 Does Not Exist Beginn und ende des life cycles Die bean ist nicht instanziert. Die bean ist eine Sammlung der Files, die zur Entwicklungszeit der bean erzeugt wurden, z.B. bean, remote interface, home interface, serialiserter EntityDescriptor usw. 3.7.4.3 Pooled Die bean ist Instanziert aber noch nicht mit einem EJBObject assoziiert. Wenn der EJB Server gestartet wird, instanziiert er mehrere Instanzen der bean und plaziert sie in dem instance pool. Die Instanzen werden mittels Class.newInstance() erzeugt. newInstance() verwendet den Default-Konstruktor ohne Argumente. Damit erhalten die persistent fields ihre DefaultWerte. Die Instanz repräsentiert in diesem Zustand keine Daten der Datenbank. Direkt nach der Erzeugung und vor dem Einfügen in den pool wird der bean mittels setEntityContext() ihr context zugewiesen. Die beans in dem instance pool sind inaktiv. Es sei denn sie werden für einen find() request verwendet. find() requests erfordern keinen definierten Zustand der bean. 3.7.4.4 Ready Die bean ist instanziert und mit einem EJBObject assoziiert. Die bean instance ist ready für Anforderungen über Methodenaufrufe. 3.7.4.4.1 Transition from pooled to ready via creation // Einfügen Bild Pool Wenn ein client die create() Methode von EJB home aufruft, wird ein EJB object auf dem server erzeugt, diesem EJB object eine bean des instance pools zugewiesen, die zur gerufenen create() Methode gehörende ejbCreate() methode der bean instance gerufen, ein primary key erzeugt und in das EJB object eingebettet. Durch den primary key erhält das EJB Object seine Identität. Im Falle von bean managed persistence (vgl. weiter hinten), erzeugt die bean selbst den primary key im Rahmen von ejbCreate() und liefert ihn an den EJB Server zurück. Nach dem Ablauf der ejbCreate() methode steht die Context Information zur Verfügung. 231 232 Anschließend wird die ejbPostCreate() Methode aufgerufen. In dieser Methode kann die bean Initialiserungen vornehmen, welche auf dem Kontext basieren. Schließlich übermittelt der EJB Server dem client ein stub object als remote reference. Damit geht die zugewiesene bean instance in den ready state über. Die bean instance kann nun requests des client und callbacks des EJBServers empfangen. 3.7.4.4.2 Transition from pooled to ready via activation Activation ermöglicht wenigen bean instances viele clients zu bedienen. Wenn eine bean instance zum pool zurückkehrt, hinterlässt sie das EJB object ohne instance. Das EJB object unterhält seine Verbindung zum stub auf client Seite. Aus der Sicht des client hat sich daher die entity bean nicht verändert. Sowie der client eine business methode aufruft, erhält das EJB object eine neue bean instance. Wenn also eine bean instance aktiviert wird, verlässt sie den instance pool und wird einem EJB object zugewiesen. Nach der Zuweisung an ein EJB object ist der EntityContext in der Lage, Informationen zu liefern, die spezifisch für das EJB object sind. Direkt nach der Zuweisung wird vom EJB Server die ejbActivate() Callback-Methode gerufen. Im Rahmen dieser Methode kann die bean Arbeiten verrichten, die für die Bedienung des Client erforderlich sind. Im Falle von container managed beans werden anschließend die persistent fields vom container aus der Datenbank gefüllt. Anschließend wird die bean mittels ejbLoad() über diesen Vorgang informiert. Im Falle von bean menaged persistence hat die bean selbst im Rahmen von ejbLoad() ihre persistent fields zu füllen. 3.7.4.4.3 Transition from ready to pooled via passivation Passivation ist der Prozess der Trennung der bean instance von ihrem EJB object. Der container kann die Trennung zu jeder Zeit vornehmen, es sei denn, die instance führt gerade eine Methode aus. Als Teil des passivation process ruft der container die callbackMethode ejbPassivate() auf. Auf diese Weise erhält die bean Gelegenheit, Resourcen frei zu geben. Die Methode dient nicht zum Abspeichern der persistent fields im Falle von bean managed persistence. Diese Abspeicherung erfolgt im Rahmen der Methode ejbStore(), die vor ejbPassivate() aufgerufen wird. 3.7.4.4.4 Transition from ready to pooled via removal Wenn der client die bean nicht mehr benötigt, ruft er eine remove() Methode für die bean. Dies hat zur Folge, dass die bean instance von dem EJBObject disazzoziiert und an den instance pool zurüchgegeben wird. Eine bean instance gelangt also auch in den pooled state, wenn der client eine der remove() Methoden aufruft. Im Falle von entity beans werden die Daten aus der Datenbank entfernt. Die Information bezüglich der Identität der bean ist während der Ausführung der callbackMethode ejbRemove() noch über den EntityContext erreichbar. Die ejbRemove() Methode muss alle Resourcen frei geben, die auch im Rahmen von ejbPassivate() freizugeben sind. Z.B. kann man die ejbPassivate() Methode aus der ejbRemove() Methode aufrufen. 3.7.4.4.5 Anmerkungen ejbLoad() und ejbStore() können nur im ready state gerufen werden. Manche EJB Server rufen vor jedem Methodenaufruf ejbLoad() und nach jedem Methodenaufruf ejbStore(). Die callBack-Methode ejbLoad() wird im Falle von container managed beans direkt nach dem 232 233 Laden der persistent fields aus der Datenbank gerufen und die Methode ejbStore() direkt vor dem Speichern der persistent fields in die Datenbank. Die callback-Methoden ejbCreate() und ejbRemove() können verwendet werden, um Daten in die Datenbank einzufügen bzw. zu löschen. Das Leben einer bean instance endet, wenn der container die Instanz aus dem pool entfernt und dem Garbage Collector übergibt. Dies geschieht z.B., wenn der container die Anzahl der bean instances dem Bedarf anpasst, wenn der EJB Server heruntergefahren wird, wenn eine bean sich unnormal verhält. Mitels der callback-Methode unsetEntityContext() informiert der container die bean über die bevorstehende Zerstörung. Vor der garbage collection wird schließlich noch wie für jedes andere java object die finalize() Methode gerufen. 3.7.5 The Life Cycle of a Stateless Session Bean 3.7.5.1 States Bild life cycle stateless session bean 3.7.5.2 DoesNotExist Es existiert keine Instanz im Speicher 3.7.5.3 Method-Ready Pool Der container wird in Abhängigkeit vom Bedarf Instanzen erzeugen oder vernichten. 3.7.5.3.1 Transition to the Method-Ready Pool Die bean wird mittels Class.newInstance() instanziiert. Dies erfordert einen Konstruktor ohne Argumente. Die Initialisierung findet im Rahmen von ejbCreate() statt. Mittels setSessionContext() wird die Referenz auf den EJBContext gesetzt. Die ejbCreate() Methode wird gerufen. Dies geschieht nur einmal während der Lebensdauer der bean. Im Unterschied zu entity beans und stateful session beans wird ein create() Aufruf des client nicht an die Methode ejbCreate() der bean delegiert. Eine Socket-Verbindung kann also z.B. im Rahmen von ejbCreate() geöffnet, während der gesamten Lebensdauer beibehalten und im Rahmen von ejbRemove() geschlossen werden. 233 234 3.7.5.3.2 Life in the Method-Ready Pool Wenn ein client eine business Methode ruft wird dieser Aufruf an irgend eine verfügbare Instanz im Method-Ready Pool delegiert. Während die Instanz die Anforderung ausführt, ist sie für keinen weiteren Aufruf verfügbar. Stateless session beans werden einem EJB object lediglich für die Dauer eines einzigen Methodenaufrufes zugewiesen. Es ist nicht vorhersehbar, welche instance die Anforderung des client bedient. Die callback-Methoden ejbActivate() bzw. ejbPassivate() werden im Falle einer stateless session bean nicht aufgerufen. Ein create() Aufruf des client führt nicht zu einem Aufruf von ejbCreate(). 3.7.5.3.3 Transition out of the Method-Ready Pool Eine stateless session bean stirbt, wenn der Server entscheidet, die Anzahl der Instanzen zu reduzieren. An dieser Stelle wird die callback-Methode ejbRemove() gerufen, in der die bean ihre Resourcen freigeben kann. Ein remove() Aufruf des client bewirkt hingegen nur die Freigabe des stubs und des EJB objects. Nach der ejbRemove() methode wird die bean dem garbage collector übergeben. Im Falle einer stateless session bean wird also durch eine timeout oder remove() Operation lediglich die remote reference zerstört jedoch nicht die bean instance. Diese wird für andere clients weiterverwendet. 3.7.6 3.7.6.1 The Life Cycle of a Stateful Session Bean The Activation Mechanism Der größte Unterschied zu den anderen beans besteht darin, dass kein instance pooling betrieben wird. Stateful session beans sind während ihres gesamten Lebens einem client gewidmet. An Stelle des pooling werden sie lediglich aus dem Speicher entfernt, um Resourcen zu sparen. Eine solche bean passivated und wieder activated. Bild activation of stateful session beans 234 235 Zu diesem Zweck wird im Rahmen der Passivierung deren conversational state z.B. mittels Java Serialiserung gespeichert, dem EJBObject zugeordnet und die stateful session bean instance zerstört. Dabei bleibt das EJB object mit dem client verbunden. Die bean instance wird jedoch dem garbage collector übergeben. Im Rahmen der Aktivierung wird eine neue Instanz der session bean erzeugt, dem EJBObject zugeordnet und mit den gespeicherten Daten gefüllt. Die callback Methoden ejbActivate() und ejbPassivate() informieren die bean über die Vorgänge und erlauben der bean notwendige Arbeiten vor der Passivierung oder nach der Aktivierung durchzuführen. 3.7.6.2 States Bild States of a Stateful Session Bean 3.7.6.3 Does Not Exist Es existiert keine Instanz im Speicher 3.7.6.4 Method Ready 3.7.6.4.1 Transition to Method Ready Nach dem Aufruf der create() Methode erzeugt der container mittels newInstance() eine bean instance, weist diese einem EJB object zu, setzt mittels setSessionContext() den EntityContext und ruft ejbCreate(). Nach der Rückkehr dieser Funktion liefert er eine remote reference an den client. Nun ist die bean bereit, Aufrufe des client zu bedienen. 3.7.6.4.2 Transition to Does Not Exist Der Übergang in den Does not Exist state findet statt, wenn die bean removed wird. Der Anwender kann eine der remove() Methoden aufrufen. Der Container kann die bean entfernen, wenn ein timeout abgelaufen ist. Die bean erhält die Gelegenheit, im Rahmen von ejbRemove() Resourcen zu schließen oder referenzierte beans zu entfernen. 3.7.6.5 3.7.6.5.1 Passivated Transition to Passivated 235 236 Wenn eine stateful session bean in diesen Zustand versetzt wird, werden die instance fields der bean gelesen und auf ein sekundäres Speichermedium geschrieben, welches dem EJB object zugeordnet ist. Mittels der callback-Methode ejbPassivate() wird die bean darüber informiert, dass sie in den passiven Zustand versetzt wird. Die bean sollte im Rahmen dieser Funktion alle offenen Resourcen schließen und ihre nontransient, nonserializable fields auf 0 setzen. Der conversational state muss aus primitve values oder serializable objects bestehen. Der container kann dazu den Java Serialisierungsmechanismus nutzen. Referenzen auf andere beans und die Referenz auf den SessionContext werden vom container gespeichert und bei der Aktivierung wieder restauriert. Nichtserialisierbare Objektreferenzen und transiente variablen bleiben nicht erhalten. Felder, die nonserializable und nontransient sind müssen auf 0 gesetzt werden. 3.7.6.5.2 Transition to Method Ready Wenn der client eine Methode einer passivated bean ruft, wird diese aktiviert. SessionContext und bean referenzen werden restauriert. Anschließend wird die bean mittels ejbActivate() über die Aktivierung informiert. Die bean kann im Rahmen dieser Methode Resourcen öffnen und transiente Felder initialisieren. Im Gegensatz zum Java Serialisierungsstandard werden transiente Felder nicht auf Default-Werte gesetzt. Sie können nach der Aktivierung beliebige Werte enthalten uns sollten daher im Rahmen von ejbActivate() sinnvoll initialisert werden. 3.8 Primary Services 3.8.1 Overview Die OMG hat 13 frameworks definiert, die als services bezeichnet werden. 6 von diesen werden als primary services bezeichnet. Diese sind erforderlich für einen effektiven Betrieb des EJBServers. Die primary services umfassen concurrency, transactions, persistence, distributed objects, naming and security. Einige dieser services werden im Folgenden ausführlicher dargelegt. 3.8.2 Concurrency Session beans Eine stateful session bean verwaltet conversational states. Daher wäre ein konkurrierender Zugriff durchaus problematisch. Jedoch wird sie nur von demjenigen client verwendet, der sie auch erzeugt hat. Daher muss sie nicht geschützt werden. Eine stateful session bean ist immer genau einem client zugeordnet. Eine stateless session bean hat keine inneren Zustände. Daher bedarf sie ohnehin keines Schutzes. Entity beans Entity beans repräsentieren Daten, z.B. eine Kreuzfahrt. Diese Daten werden durchaus von mehreren clients, z.B. Reisebüros, konkurrierend angefasst. Entity beans sind daher shared components. EJB verhindert den konkurrierenden Zugriff auf eine bean instance. Verschiedene clients können sehr wohl mit ein und demselben EJBObject verbunden sein. Jedoch hat nur ein client thread Zugriff auf die bean instance. Wenn also ein Client eine Methode der bean aufruft, hat ein anderer client erst dann wieder die Möglichkeit für einen Zugriff, wenn die Methode beendet wurde. Ein entsprechender Schutz existiert über einzelne Methodenaufrufe hinaus für Transaktionen. // Einfügen 236 237 Bild Concurrent Access Bedingt durch diese automatische Behandlung des konkurrierenden Zugriffs müssen die bean Methoden nicht thread-safe gemacht werden. Die EJB Spezifikation verhindert sogar die Verwendung des Schlüsselwortes synchronized. Achtung : Bean instances sind nicht reentrant-fähig. Ein loopback tritt auf, wenn eine bean A eine Methode von bean B ruft und dann B versucht einen callback zu A zu machen. Bean A unterscheidet nämlich nicht zwischen dem callback von B (reentarnt code) und einem Versuch eines anderen Clients, eine Methode aufzurufen (multithreaded access). Die fehlende reentrant-fähigkeit bezieht sich nur auf die Kommunikation mit anderen beans. // Einfügen Bild reentrant 3.8.3 Persistence Der Status einer entity bean wird permanent in einer Datenbank gespeichert. Wenn der container für die Synchronisation der bean felder mit den Datenbankeinträgen verantwortlich ist, spricht man von container managed persistence. Im anderen Falle spricht man von bean managed persistence. Object to relationale persistence Die entity bean fields werden auf Tabellen und spalten einer relationalen Datenbank abgebildet. 237 238 Bild Mapping Attributes to Relational Database Die Anforderung an EJBHome zur Erzeugung oder Vernichtung einer entity bean hat die Erzeugung oder Vernichtung eines Datenbank-Records zur Folge. Java objects lassen sich nicht immer nahtlos auf relationale Datenbanktabellen abbilden. Object database persistence Objektorientierte Datenbanken speichern Objekt Typen und Objekt Graphen. In einer OO Sprache geschriebene Komponenten lassen sich sauberer auf die Datenbank abbilden. Diese Datenbanken handeln zirkulare Referenzen. Jedoch sind Objektdatenbanken noch nicht soweit standardisiert wie relationale und es existieren weniger third-party Produkte zur Unterstützung. 3.8.4 Distributed Objects Beispiel für ein remote interface public interface TravelAgent extends EJBObject { public Reservation travelAgentReservation(String idRes, String idCruise, String idCustomer) throws RemoteException, DuplicateKeyException, CreateException; public Reservation getRecordReserv(String idRes) throws RemoteException; } RMI fordert, dass alle Parameter und Returnwerte entweder primitive Jafa Typen (int, double, byte ...) sein müssen oder aber serialisierbare Objekte. Serialisierbare Objekte können über das Netz auf einen remote Computer kopiert werden. Änderungen an der remote Kopie werden im Orginal nicht reflektiert. 238 239 Bild passing serializable objects Achtung : Rechts muss stehen Copy of serializable object Über diese Technik kann man auch remote referenzen auf beans übertragen. Eine remote reference ist ein Remote interface, welches durch ein EJBObject als stub implementiert wird. Wenn eine solche remote reference als Parameter oder Returnwert einer Methode verwendet wird, so wird der stub per kopie über das Netz transportiert. Die Kopie des EJBObject stub zeigt auf dieselbe bean. Genaugenommen zeigt der stub auf das Wrapper EJBObject der bean. Bild passing remote references 3.8.5 Naming Locating Beans with JNDI Beispielcode für die Verwendung einer bean. 239 240 InitialContext initial = new InitialContext(); Object objref = initial.lookup("reservation"); ... record = hreservation.findByPrimaryKey(idHilfe); ... theReservation = hreservation.create(idReservation, idCruise, idCustomer); Die Referenz auf EJBHome für das cruise bean erhält man über die Verwendung des Java Naming and Directory Interface (JNDI). JNDI erlaubt einem application client, den EJBServer als eine Menge von directories wie in einem gewöhnlichen File System zu sehen. Der initial context ist der Startpunkt der Suche, vergleichbar mit der root eines File Systems. Alle EJBServer müssen unabhängig vom Hersteller den JNDI Zugriff für ihren speziellen naming und directory service unterstützen. 240