// Aufgabe 5.1 // =========== package y2k ; import java.util.* ; import java.awt.* ; import java.awt.event.* ; /** * Uhr, die die Anzahl der Sekunden bis zum Jahreswechsel 1999/2000 * anzeigt. * * Das Fenster enthält eine Sekundenanzeige, drei Knöpfe (Start, * Stop, Ende) und ein Menü, mit dem das Aktualisierungsintervall * vorgegeben werden kann. * * @author Uwe Waldmann * @version 1.0 */ public class Y2KClock extends Frame { // Es sind insbesondere zwei Aspekte dieser Implementierung, die // unter stilistischen Gesichtspunkten nicht gerade vorbildlich // sind: // // Erstens erzeugen und starten wir jedesmal, wenn der Startknopf // gedrückt wird, einen neuen Thread, und beenden ihn wieder, wenn // der Stopknopf gedrückt wird, anstatt mit einem einzigen Thread // (der bei Bedarf wartet) auszukommen. // // Zweitens ist es unschön, daß viele Variable für die ganze Klasse // Y2KClock global sind (z.B. currentInterval) und die Kommunikation // zwischen den inneren Klassen häufig über diese globalen Variablen // läuft. // // Man könnte das anders machen und die einzelnen Teile des Programms // stärker voneinander entkoppeln, aber die nötigen Techniken (z.B. // das Model-View-Controller-Konzept) waren zum Ausgabezeitpunkt // des Übungsblattes in der Vorlesung noch nicht behandelt. Dagegen // kommt diese Implementierung vollständig mit den sprachlichen // und konzeptionellen Mitteln aus, die zum Ausgabezeitpunkt zur // Verfügung standen. /** * Sekundenanzeige. */ Label timeDisplay; /** * Knopf zum Starten der Uhr. */ Button startButton; /** * Knopf zum Anhalten der Uhr. */ Button stopButton; /** * Knopf zum Beenden des Programms. */ Button quitButton; /** * Menü zur Intervallauswahl. */ Choice intervalChoice; /** * Zeitintervalle, entsprechen den Einträgen in intervalChoice. */ int[] intervals; /** * Der gerade laufende Uhrthread. */ Y2KThread currentThread; /** * Aktuelles Updateintervall. */ int currentInterval; /** * Anzahl der Millisekunden zwwischen dem 1.1.1970, 0:00 Uhr und * dem 1.1.2000, 0:00 Uhr. */ final long y2KMillis = new GregorianCalendar(2000,0,1).getTime().getTime(); /** * Produziert eine neue Uhr. */ public Y2KClock() { // Das Layout ist ziemlich schmucklos, aber zu Demonstrationszwecken // reicht es aus. setLayout(new BorderLayout()); Panel buttonPanel = new Panel(); startButton = new Button("Start"); startButton.addActionListener(new StartButtonListener()); buttonPanel.add(startButton); stopButton = new Button("Stop"); stopButton.addActionListener(new StopButtonListener()); stopButton.setEnabled(false); buttonPanel.add(stopButton); quitButton = new Button("Ende"); quitButton.addActionListener(new QuitButtonListener()); buttonPanel.add(quitButton); add(buttonPanel,"North"); // Etwas Leerraum, damit im Fenster nachher genug Platz für die // Zahl ist. timeDisplay = new Label(" "); add(timeDisplay, "Center"); Panel choicePanel = new Panel(); choicePanel.add(new Label("Änderung: ")); // Zeitintervalle in Millisekunden, entsprechen den Einträgen in // intervalChoice. intervals = new int[] {1000, 2000, 5000, 10000}; currentInterval = 1000; intervalChoice = new Choice(); intervalChoice.add("jede Sekunde"); intervalChoice.add("alle zwei Sekunden"); intervalChoice.add("alle fünf Sekunden"); intervalChoice.add("alle zehn Sekunden"); intervalChoice.addItemListener(new IntervalItemListener()); choicePanel.add(intervalChoice); add(choicePanel, "South"); addWindowListener(new MainWindowListener()); setSize(300,200); setVisible(true); } /** * Uhrthread-Klasse. */ class Y2KThread extends Thread { boolean stop; /** * Setzt stop auf true (mit der Folge, daß run() bei nächster * Gelegenheit terminiert) und löscht die Anzeige. * * @see run */ public synchronized void terminate() { stop = true; timeDisplay.setText(""); } /** * Solange stop == false ist, berechne Anzahl der Sekunden bis zum * Jahreswechsel, zeige sie an, und schlafe currentInterval ms. */ public void run() { long numberOfSeconds; stop = false; // Eigenlich ist das eine "while(!stop)"-Schleife, aber wir // brauchen die Abfrage von stop und die Anzeige der Sekunden // innerhalb eines synchronized-Blocks. Anderenfalls könnte es // passieren, daß stop == false ist, run() den Schleifenrumpf // betritt, terminate() die Variable stop = true setzt und die // Anzeige löscht, und run() danach die Anzeige noch einmal // aktualisiert, bevor die Schleife verlassen wird. // Andererseits können wir nicht die ganze Methode synchronized // machen, weil sonst terminate() nie zum Zuge kommt. while (true) { synchronized(this) { if (stop) { break; } numberOfSeconds = (y2KMillis - System.currentTimeMillis()) / 1000; timeDisplay.setText(Long.toString(numberOfSeconds)); } try { sleep(currentInterval); } catch (InterruptedException e) { } } } } /** * Listener-Klasse für den Startknopf. */ class StartButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { // Startknopf inaktivieren. startButton.setEnabled(false); // Stopknopf aktivieren. stopButton.setEnabled(true); // currentThread sollte null sein, aber sicherheitshalber fragen // wir es ab und terminieren currentThread, falls nötig. if (currentThread != null) { currentThread.terminate(); } // Neuen Uhrthread erzeugen und starten. currentThread = new Y2KThread(); currentThread.start(); } } /** * Listener-Klasse für den Stopknopf. */ class StopButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { // Stopknopf inaktivieren. stopButton.setEnabled(false); // currentThread sollte nicht null sein, aber sicherheitshalber // fragen wir es ab, bevor wir currentThread terminieren. if (currentThread != null) { currentThread.terminate(); // Der alte Thread läuft jetzt noch so lange, bis er zum nächsten // Mal die Variable stop abfragt. Man könnte das mittels // currentThread.interrupt() beschleunigen. // currentThread wird Futter für den Garbage Collector. currentThread = null; } // Nachdem der alte Thread den Terminierungsbefehl bekommen hat: // Startknopf aktivieren. startButton.setEnabled(true); } } /** * Listener-Klasse für den Endeknopf. */ class QuitButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0); } } /** * Listener-Klasse für das Intervallmenü. */ class IntervalItemListener implements ItemListener { public void itemStateChanged(ItemEvent e) { // Nummer des selektierten Menüeintrags ermitteln, die dazugehörende // Zeit in intervals[] ablesen und Intervall setzen. setInterval(intervals[intervalChoice.getSelectedIndex()]); // Auch hier könnte man den laufenden Uhrthread (wenn ein solcher // existiert) vorzeitig mittels currentThread.interrupt() aus einem // sleep() herausholen. Anderenfalls beendet der laufende Uhrthread // erst einmal das alte Updateintervall, ohne die Änderung // zu Kenntnis zu nehmen. } } /** * Listener-Klasse für das Hauptfenster. */ class MainWindowListener extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } /** * Setzt das Update-Intervall. */ void setInterval(int newCurrentInterval) { currentInterval = newCurrentInterval; } /** * Startet das Programm. */ public static void main(String[] args) { new Y2KClock(); } }