Zeit weiterschalten und Logfile einsehen
  Der nächste Schritt der Vervollständigung des Programms besteht darin, dem Manager zu ermöglichen, die Zeit weiterzuschalten. Es wird das "Manager"-Menü des Büros erstellt. Es wird ein Menüpunkt benötigt, der eine Aktion auslöst, die das vom Shopsp apilogo verwendete Timersp apilogo-Objekt ermittelt und es weiterschaltet. Um den Menüpunkt als ersten des Menüs einzubauen, wird in der Methode getDefaultMenuSheetsp apilogo zuerst eine neues Menü für die Wartung angelegt und dort ein neuer Eintrag eingefügt. In die Klasse Office wird folgender Code geschrieben:  
   neue Javaklasse    Office.java  
 

  public MenuSheet getDefaultMenuSheet()
  {
    MenuSheet msSubMenu = new MenuSheet("Maintenance");

    msSubMenu.add(new MenuSheetItem("Advanced time",
      new sale.Action()
      {
        public void doAction(SaleProcess p, SalesPoint sp)
        {
          Shop.getTheShop().getTimer().goAhead();
        }
      }));

    MenuSheet msMenu = new MenuSheet("Office menu");
    msMenu.add(msSubMenu);
    return msMenu; 
  }
        
 
  Damit kann die Zeit weitergestellt werden. Leider ist damit noch keine der am Tagesanfang anstehenden Aktionen ausgeführt. Dazu gehört das Aktualisieren des Standard-FormSheetssp apilogo.  
  Es ist zu beachten, daß die vom FormSheetsp apilogo angezeigten Daten ungültig werden könnten. Dies kann geschehen, wenn entweder die Zeit weitergeschaltet wird oder sich der Geldbestand im Münzschacht ändert.  
  Die Zeit kann nur vom Manager weitergeschaltet werden. Es muß also daran gedacht werden, danach das FormSheetsp apilogo neu aufzubauen. Dies kann entweder direkt beim Weiterschalten geschehen oder indirekt durch den Einsatz von Listenern.  
  Ein Listener ist Teil eines erweiterten Observer-Patterns. Zwei weitere Bestandteile sind die Quelle des Ereignisses (Observable) und des Ereignis (die Erweiterung vom Observer-Pattern) selbst. Unter einem Ereignis ist jedoch nicht der Vorfall im üblichen Sinne zu verstehen, sondern vielmehr die Beschreibung des Vorfalls. Diese Beschreibung wird von der Quelle erzeugt und an alle Listener gesendet. Das geschieht, in dem an den Listenern eine festgelegte Methode aufgerufen wird. Die Besonderheit an dem Konzept ist, daß die Anzahl von Listenern vorher nicht festgelegt ist. Vielmehr kann sich während des Programmablaufs eine, meist beliebige, Anzahl von Listenern an der Quelle an- und wieder abmelden. Ein weiteres Merkmal ist die Kapselung der Daten. So werden nicht mehr Parameter an die zuständige Methode des Listeners übergeben, sondern lediglich das Ereignis, das dann Methoden zur Verfügung stellt, um die enthaltenen Daten abzufragen.Die Methoden, die ein Listener implementieren muß, um bestimmte Ereignisse empfangen zu können, sind in einem zum Ereignismodell gehörenden Listener-Interface beschrieben.  
  Um dies unabhängig von der Betätigung des Menüpunktes auszuführen, werden die Aktionen nicht direkt in die durch den Menüpunkt ausgelöste Aktion eingefügt. Vielmehr wird am Timersp apilogo ein Listener angemeldet, der die anfallende Arbeit übernimmt. Damit werden die Anweisungen relativ unabhängig von weiteren Änderungen am Programm ausgeführt. Es ist so z.B. möglich, den Timersp apilogo auf andere Weise weiterzuschalten, als mit dem eingefügten Menüpunkt, ohne das das einen Effekt auf den Listener und die in ihm enthaltenen Anweisungen hätte.  
  Ein Listener wird am Timersp apilogo mit addTimerListenersp apilogo angemeldet. Er muß das TimerListenersp apilogo-Interface implementieren. Eine Klasse, die das bereits erledigt und deren Objekte somit als Listener verwendet werden können, ist TimerAdaptersp apilogo. Diese Klasse enthält nur leere Methoden, die nach Bedarf angepaßt werden können.  
  Um den Listener nur einmal, und zwar bei der Initialisierung des Büros, anzumelden, wird der nötige Code als letzte Anweisung in den Konstruktor von Office eingefügt. Der Listener wird als anonyme Unterklasse von TimeAdaptersp apilogo implementiert:  
 

  Shop.getTheShop().getTimer().addTimerListener(
    new TimerAdapter()
    {
    });
        
 
  In dem so angemeldeten Listener muß nun die Methode onGoneAheadsp apilogo angepaßt werden, die vom Timersp apilogo aufgerufen wird, wenn die Zeit weitergeschaltet wurde. Es wird also folgendes in die anonyme Klasse eingefügt:  
 

  public void onGoneAhead(TimerEvent trEvt)
  {
    refreshOfficeDisplay();
  }
        
 
  Die verwendeten Klassen bzw. Interfaces TimerListenersp apilogo, TimerAdaptersp apilogo und TimerEventsp apilogo sind in sale.eventssp apilogo enthalten. Das Paket wird daher importiert:  
 

  import sale.events.*;
        
 
  In der onGoneAhead-Methode wird das FormSheetsp apilogo neu dargestellt, wenn die Zeit weitergeschaltet wurde. Wir erneuern das FormSheetsp apilogo deshalb in einer eigenen Methode, um diese Tätigkeit auch zu anderen Gelegenheiten aufrufen zu können.  
  Wir implementieren deshalb zunächst die Methode refreshOfficeDisplay.  
 

  protected void refreshOfficeDisplay() 
  {
  }
        
 
  In dieser Methode muß sichergestellt werden, daß nicht gerade ein Prozeß abläuft. Dieser stellt seine eigenen FormSheetssp apilogo bereit, die nicht einfach durch ein neues Standard-FormSheetsp apilogo überschrieben werden dürfen. Sollte ein Prozeß laufen, wird bei dessen Ende in jedem Fall das Standard-FormSheetsp apilogo gesetzt, so daß in diesem Fall keine weiteren Schritte zu ergreifen sind. Den gerade ablaufenden Prozeß erhält man über die Methode getCurrentProcesssp apilogo. Liefert sie null, läuft kein Prozeß.  
  Eine weitere Bedingung, die es zu beachten gilt, ist, daß das Büro ein gültiges Displaysp apilogo haben muß. Das wäre beispielsweise dann nicht der Fall, falls man bei dem betreffenden SalesPointsp apilogo ein NullDisplaysp apilogo angemeldet hätte. Tritt das abzufangende Ereignis ein, so wird die entsprechende Methode im Listener trotzdem aufgerufen und darf nicht versuchen, ein FormSheetsp apilogo zu setzen. Die Überprüfung, ob ein benutzbares Displaysp apilogo vorhanden ist, erfolgt mit der Methode hasUsableDisplaysp apilogo. Ihr wird der anfragende Prozeß übergeben. Da die Anfrage aus keinem Prozeß heraus stattfindet, ist der Parameter in diesem Fall null.  
  Wenn kein Prozeß läuft, aber ein Displaysp apilogo vorhanden ist, so kann über setFormSheetsp apilogo das neue FormSheetsp apilogo gesetzt werden. Dazu sind der Prozeß, hier also null, und das FormSheetsp apilogo selbst zu übergeben. Diese Anweisung wird in einen try-Block geschrieben, da sie eine InterruptedException erzeugen könnte. Da dieser Fall normalerweise aber nicht eintreten kann, wird im catch-Block lediglich eine Fehlermeldung auf die Standardfehlerausgabe geschrieben. In die Methode wird folgender Code eingefügt:  
 

  if (getCurrentProcess() == null && hasUseableDisplay (null)) {
    try {
      setFormSheet (null, getDefaultFormSheet());
    }
    catch (InterruptedException iexc) {
      System.err.println ("Update interrupted");
    }
  }
        
 
  Der Listener für die Weiterschaltung der Zeit ist fertig implementiert. Jetzt werden noch Listener benötigt, die bei Veränderung des Geldbestandes im Münzschacht die Oberfläche neu darstellen. In diesem Fall ist offensichtlich der Stocksp apilogo mit dem Namen "coin slot" Quelle der Ereignisse. Dieser Stocksp apilogo wurde als Instanz der Klasse MoneyBagImplsp apilogo angelegt, die das ListenableStocksp apilogo-Interface implementiert. An Stockssp apilogo, die dieses Interface implementieren, kann sich ein StockChangeListenersp apilogo mit addStockChangeListenersp apilogo an- und mit removeStockChangeListenersp apilogo wieder abmelden. Eine Implementierung dieses Listeners ist mit StockChangeAdaptersp apilogo bereits im Framework vorhanden. Ein Objekt dieser Klasse kann sich an- und abmelden und implementiert alle vom Interface geforderten Funktionen. Diese Funktionen sind jedoch nicht leer und können in einer Unterklasse angepaßt werden. Auf diese Weise ist es möglich, in einer eigenen Listenerklasse nur die Funktionen zu implementieren, die auf das gewünschte Ereignis reagieren und alle anderen vom Interface geforderten Methoden vom Adapter zu erben.  
  Um StockChangeAdaptersp apilogo verwenden zu können, wird das Paket data.eventssp apilogo importiert, für MoneyBagImplsp apilogo wird data.ooimplsp apilogo benötigt:  
 

  import data.events.*;
  import data.ooimpl.*;
        
 
  Es wird im Konstruktor von Office ein Listener am Münzschacht angemeldet, der von StockChangeAdaptersp apilogo abgeleitet ist:  
 

  ((MoneyBagImpl)Shop.getTheShop().getStock(
    "coin slot")).addStockChangeListener(new StockChangeAdapter()
    {
    });
        
 
  In der anonymen Klasse muß die Methode überschrieben werden, die auf das endgültige Hinzufügen von Einträgen in den Stocksp apilogo reagiert. Es handelt sich um die Methode commitAddStockItemssp apilogo, der von der Quelle das Ereignis übergeben wird. Hier bedienen wir uns wieder der Methode refreshOfficeDisplay, um die Anzeige zu aktualisieren.  
 

  public void commitAddStockItems(StockChangeEvent sce)
  {
    refreshOfficeDisplay();
  }
        
 
  Analog dazu wird die Methode commitRemoveStockItemssp apilogo in die anonyme Klasse eingefügt. Diese reagiert, wenn vom Geldbestand des Münzschachtes Beträge abgezogen werden. Da wir sowohl bei dem Hinzufügen als auch beim Herausnehmen von Münzen aus dem Münzschacht auf gleiche Weise reagieren wollen, rufen wir hier die oben implementierte Methode commitAddStockItemssp apilogo auf. Damit gewährleisten wir, daß bei einer späteren Änderung nur eine der beiden Methoden angepasst werden muß.  
 

  public void commitRemoveStockItems(StockChangeEvent sce) 
  {
    commitAddStockItems(sce);
  }
       
 
  Damit sind die Reaktionen auf eine Änderung des Geldbestandes adäquat umgesetzt.  
   neue Javaklasse    MyLoggable.java  
  Im folgenden sollen im RentProcess und im GiveBackProcess die Vorgänge mitprotokolliert werden. Hierfür muss die Klasse MyLoggable jetzt implementiert werden. Diese soll später alle Aktivitäten der Kunden im RentProcess und GiveBackProcess während des Ausleih- und Rückgabevorgangs mitloggen.  
  Um die erforderlichen Klassen aus dem Paket logsp apilogo verwenden zu können, muss die Klasse MyLoggable folgende import-Anweisungen enthalten:  
 

  import log.Loggable;
  import log.LogEntry;
        
 
  Es wird vom Interface Loggablesp apilogo geerbt, das ein Objekt repräsentiert, das geloggt werden kann. Bestandteil dieser Klasse ist der Konstruktor. In ihm werden die für die Erstellung des Log-Eintrags wichtigen Variablen übergeben. Diese müssen zuerst deklariert werden:  
 

  public class MyLoggable implements Loggable 
  {
    String name;
    String customerID;
    Object date;
    boolean rent;
  }  
        
 
  Bei dem Object date handelt es sich um die aktuelle Shopzeit, dargestellt in Turns und die boolsche Variable rent gibt an, ob ein Ausleih- oder ein Rückgabevorgang protokolliert werden soll. Um die Variablen zu initialisieren, werden zwei Konstruktoren erstellt, je nachdem, welcher Prozess das Logging aufruft:  
 

  public MyLoggable(String name, String customerID, Object date)
  { 
    super();
    this.name       = name;
    this.customerID = customerID;
    this.date       = date;
    rent            = true;
  }

  public MyLoggable(StoringStockItemDBEntry cassetteItem,
                       Customer customer, Object date)              
  {
    super();
    name = cassetteItem.getSecondaryKey();
    this.customerID = customer.getCustomerID();
    this.date = date;
    rent = false;
  }
        
 
  Der erste Konstruktor erwartet Parameter, die schon in der entsprechenden Form übergeben werden. Der zweite Konstruktor läßt als ersten Parameter auch einen Eintrag aus dem DataBasketsp apilogo des Kunden zu, aus dem dann im Konstruktor die relevanten Daten geholt werden. Durch die boolsche Variable rent wird weiterhin unterschieden, ob der Aufruf aus dem RentProcess oder GiveBackProcess erfolgt, und damit der spätere Logeintrag entsprechend angepasst.  
  Für den zweiten Konstruktor muss noch folgende Anweisung ergänzt werden:  
 

  import data.ooimpl.StoringStockItemDBEntry;
        
 
  Die im Interface Loggablesp apilogo definierte Methode getLogDatasp apilogo muss zur Vervollständigung der Klasse implementiert werden. Sie holt sich den Log-Eintrag mit Hilfe der Klasse MyLogEntry:  
 

  public LogEntry getLogData()
  {
    return new MyLogEntry(name, customerID, date, rent);
  }
        
 
  Am Ende der Datei MyLoggable.java wird die Klasse MyLogEntry implementiert, die einen selbstdefinierten Log-Eintrag beschreibt.  
   neue Javaklasse    MyLogEntry.java  
 

  class MyLogEntry extends LogEntry
  {
  }
        
 
  Es soll mitgeloggt werden, welcher Kunde welches Video zu welcher Zeit ausgeliehen hat. Es müssen entsprechende Variablen dafür deklariert werden:  
 

  String name;
  String customerID;
  Object date;
  boolean rent;
        
 
  Im Konstruktor werden die Variablen initialisert:  
 

  public MyLogEntry(String name, String customerID, Object date, boolean rent)
  { 
    super(); 
    this.name       = name;
    this.customerID = customerID;
    this.date       = date;
    this.rent       = rent;
  }        
        
 
  Das Aussehen eines Log-Eintrags wird durch die folgende toString-Methode bestimmt:  
 

  public String toString()
  {
    if (rent)
      return name +
        " rent by customer " + customerID +
        " (ID) at turn " + date;
    else
      return "customer "+ customerID +
        " (ID) gave back " + name +
        " at turn " + date;
  }
        
 
  Die Klasse MyLogEntry ist fertiggestellt.  
   neue Javaklasse    RentProcess.java  
  Um den Ausleihvorgang in die Log-Datei einzutragen, wird in der toPayingTransition der Klasse RentProcess, vor der endgültigen Übernahme der Videokassetten in das Kundenkonto des Kunden (nach Beginn der for-Schleife: for (; number-- > 0;)), folgender Code eingefügt:  
 

  try {
    Log.getGlobalLog().log(new MyLoggable(
      cassetteItem.getSecondaryKey(),
      customer.getCustomerID(),
      date));
  }
  catch (LogNoOutputStreamException lnose) {
  }
  catch (IOException ioe) {
  }
        
 
  logsp apilogo erzeugt zwei verschiedene Exceptions, die hier jeweils explizit abgefangen werden (daher die zwei catch-Blöcke).  
  Die IOexception benötigt noch das Paket java.io:  
 

  import java.io.*;
        
 
  Am Ende der Klasse RentProcess muß die Methode getLogGatesp apilogo implementiert werden, die das LogGatesp apilogo übergibt. Da beim Beenden des Prozesses jedoch kein Log-Eintrag geschrieben werden soll, wird das StopGatesp apilogo zurückgeliefert.  
 

  public Gate getLogGate()
  {
    return getStopGate();
  }
        
 
   neue Javaklasse    Office.java  
  Um die Log-Datei einsehen zu können, wird ein weiterer Menüpunkt in der Methode getDefaultMenuSheetsp apilogo der Klasse Office hinzugefügt:  
 

  msSubMenu.add(new MenuSheetItem("See log file", new sale.Action()
  {
    public void doAction(SaleProcess p, SalesPoint sp)
    {
    }
  }));
        
 
  Zum Anzeigen von Log-Dateien wird vom Framework bereits ein FormSheetsp apilogo zur Verfügung gestellt: LogTableFormsp apilogo. Der Konstrutor dieses FormSheetssp apilogo benötigt als Parameter mindestens einen Titel und einen LogInputStreamsp apilogo Um einen LogInputStreamsp apilogo zu erzeugen, wird wiederum ein FileInputStream benötigt. Es wird mit Hilfe des Dateinamens der Log-Datei ein FileInputStream, mit dessen Hilfe ein LogInputStreamsp apilogo und mit diesem ein LogTableFormsp apilogo erstellt. Danach wird der nicht benötigte "Cancel"-Button entfernt -- die Log-Tabelle kann lediglich zur Kenntnis genommen werden -- und das FormSheetsp apilogo kann mittels setFormSheetsp apilogo angezeigt werden. Ein Anpassen des "Ok"-Buttons ist nicht notwendig, da die standardmäßig ausgeführte Aktion bereits aus einem einfachen Schließen des FormSheetssp apilogo besteht.  
  Die Befehlssequenz wird in einen try-Block geschrieben, da einige der verwendeten Methoden Ausnahmen auslösen können. In die doAction-Methode wird folgendes eingefügt:  
 

  try {
    FileInputStream fis = new FileInputStream("machine.log");
    LogInputStream  lis = new LogInputStream(fis);
    LogTableForm    ltf = new LogTableForm("View log file", lis);

    ltf.removeButton(FormSheet.BTNID_CANCEL);
    setFormSheet(null, ltf);
  }
        
 
  Es müssen für die verwendeten Klassen die benötigten Pakete importiert werden:  
 

  import log.*;
  import log.stdforms.*;
  import java.io.*;
        
 
  Außerdem müssen die Ausnahmen abgefangen werden. Der Konstruktor des FileInputStreams könnte eine FileNotFoundException auslösen, der Konstruktor von LogInputStreamsp apilogo eine IOException und setFormSheetsp apilogo eine InterruptedException. In der Praxis dieses Programms wird das FormSheetsp apilogo jedoch nicht unterbrochen, da keine externen Abläufe auf das Office zugreifen.  
  Es werden drei catch-Blöcke an den try-Block angehangen:  
 

  catch (FileNotFoundException fnfexc) {
    try {
      setFormSheet(null, new MsgForm("Error", "Log file not found."));
    }
    catch (InterruptedException inner_iexc) {
    }
  }
  catch (IOException ioexc) {
    try {
      setFormSheet(null, new MsgForm("Error", 
        "Log file corrupt. It might be empty."));
    }
    catch (InterruptedException inner_iexc) {
    }
  }
  catch (InterruptedException iexc) {
    try {
      setFormSheet(null, new MsgForm("Error", iexc.toString()));
    }
    catch (InterruptedException inner_iexc) {
    }
  }
        
 
  Damit hat der Manager die Möglichkeit des Log-File einzusehen.  
   neue Javaklasse    VideoMachine.java  
  Jetzt muß noch in der Klasse VideoMachine das Log-File geöffnet werden. Hierzu wird die main-Methode folgendermaßen ergänzt:  
 

  try {
    Log.setGlobalOutputStream(new FileOutputStream("machine.log", true));
  }
  catch (IOException ioex) {
    System.err.println("Unable to create log file.");
  }
        
 
 Quelltexte
  Hier der Quelltext der in diesem Kapitel geänderten Klassen:  
 
vorherige Seite  Der Manager Bestand einsehen und editieren  naechste Seite
 

by kk15

Valid HTML 4.01!