Nachdem es dem Kunden möglich ist, das Angebot zu betrachten, soll er auch Videos ausleihen können. Dazu muß sich der Kunde am Automaten mit seiner Kundennummer identifizieren, sich Videos aussuchen und den entsprechenden Preis bezahlen.

Vorgänge dieser Art werden im Framework von Prozessen bearbeitet. Ein Prozeß kann benutzt werden, um beliebige Datenstrukturen zu manipulieren. Derartige Datenstrukturen können u.a. Objekte der vom Framework bereitgestellten Datentypen DataBasket, Stock und Catalog sein.

Prozesse

Prozesse werden im Framework von der Klasse SaleProcess implementiert. Sie werden als endliche deterministische Automaten verstanden, deren Zustandsübergänge sowohl durch Benutzereingaben, als auch spontan ausgelöst werden können. Die Zustände werden durch Objekte vom Typ Gate, die Zustandsübergänge durch Objekte vom Typ Transition realisiert.

Gate und Transition

Eine Transition wird als eine unteilbare (atomare) Operation abgearbeitet, sie wird vom Framework nicht unterbrochen. Daher darf es bei der Abarbeitung einer Transition auf keinen Fall zu einer Kommunikation mit dem Benutzer oder irgendeiner anderen langwierigen, möglicherweise endlosen Aktion kommen.

Im Gegensatz zu Transitions dürfen Gates unterbrochen werden (signalisiert durch eine InterruptedException). Für die Nutzerinteraktion gibt es eine spezielle Art von Gates, die UIGates. Es ist vorgesehen, an ihm ein FormSheet oder ein MenuSheet (oder beides) zu setzen. Die Entscheidung, welche der von einem UIGate wegführenden Transitionen auszuwählen ist, hängt von der Benutzereingabe ab.

UIGate

Das Prozeßmodell ist geeignet, selbst komplizierte Abläufe zu planen und umzusetzen.

Für den Ausleihvorgang werden fünf Gates zusätzlich zu den vom Framework bereits zur Verfügung gestellten benötigt: eines, an dem sich der Kunde anmeldet, ein zweites, an dem er die Videos auswählt und ein drittes, an dem die Videos bezahlt werden. Zwei weitere Gates werden benötigt, um zu entscheiden, ob genug Geld bezahlt wurde und ggf. Wechselgeld zu geben.

Gates planen

Außerdem werden vier nichttriviale Transitions benötigt. "Nichttrivial" heißt, es kann nicht einfach von einem Gate zum nächsten gewechselt werden, sondern es ist zusätzlich das nächste Gate vorzubereiten.

Transitionen planen

Die Zustände und Zustandsübergänge des Automaten werden in der folgenden Abbildung noch einmal verdeutlicht:

Der Ausleihvorgang als deterministischer Automat
Abbildung 2.1: Der Ausleihvorgang als deterministischer Automat

Zunächst jedoch muß der neue SaleProcess angelegt werden. Alle, im Laufe der Implementation wichtigen, import-Anweisungen werden sofort hinzugefügt, da inzwischen klar sein sollte, welche Framework-Pakete für welche Anweisungen benötigt werden.


  import sale.*;
  import sale.stdforms.*;
  import data.*;
  import data.ooimpl.*;
  import data.stdforms.*;
  import users.*;

  import java.util.*;
  import java.text.*;
  import java.lang.*;

  public class RentProcess extends SaleProcess
  {
  }
        

Im Konstruktor wird lediglich der Konstruktor der Oberklasse aufgerufen. Ihm wird der Name für den Prozeß übergeben:


  public RentProcess()
  {
    super("RentProcess"); 
  } 
        

Außerdem sind die Gates und Transitions zu deklarieren:

Variablendeklarationen


  protected UIGate capabilityGate;
  protected UIGate selectionGate;
  protected UIGate rentGate;
  protected Gate   decisionGate;
  protected UIGate getChangeGate;

  protected Transition toSelectionTransition;
  protected Transition toPayingTransition;
  protected Transition toDecisionTransition;
  protected Transition toGetChangeTransition;
        

Es soll weitere Variablen geben, die für den Ablauf des Prozesses wichtige Werte enthalten. Diese sind die zu verwendende Währung, der zu zahlende Betrag, der gezahlte Betrag und ein Wert, der aussagt, wie die beiden Beträge in Relation zueinander stehen. Außerdem wird noch eine Variable für den Kunden benötigt, der Videos ausleihen möchte.


  protected data.Currency myCurrency;
  private   IntegerValue  toPayValue;
  private   IntegerValue  paidValue;
  private   int           payAssessment;
  private   Customer      customer;
        

Um den Automaten zum gegebenen Zeitpunkt korrekt und einfach aufzubauen, wird eine Methode setupMachine implementiert, die diese Arbeit übernimmt:


  protected void setupMachine()
  {
  }        
        

In dieser Methode wird zunächst die Variable myCurrency mit der zu verwendenden Währung belegt. Dies geschieht vornehmlich aus Gründen der Effizienz und der Übersichtlichkeit - auf diese Weise muß nicht jedesmal erst der Shop und von diesem der entsprechende Catalog geholt werden.

Währung


  myCurrency = (Currency)Shop.getTheShop().getCatalog("DM");
        

Nun sollen die UIGates in der Methode setupMachine angelegt werden. Zunächst das, an dem sich die Kunden anmelden:

UIGates initialisieren


  capabilityGate = new UIGate(null, null);
        

Da das so erzeugte Gate den Startzustand des Automaten darstellen soll, wird es keine Transition geben, die zu ihm hin führt. Alles, was zur Abarbeitung des Gates nötig ist, muß also vor dem Starten des Automaten erledigt werden. Da die Methode setupMachine vor dem Starten ausgeführt werden soll um den Automaten überhaupt erst vorzubereiten, ist hier die geeignete Gelegenheit zur Vorbereitung des Gates. In diesem Fall ist es das Setzen eines FormSheets zur Eingabe der Kundennummer. Dazu wird ein TextInputForm verwendet. Dies ist ein FormSheet, das ein Label und eine Eingabezeile vom Typ JTextInput enthält.

Startzustand


  final TextInputForm tif =
    new TextInputForm("Customer-ID", "Customer-ID", "");
        

Es werden die weiteren Gates initialisiert:


  selectionGate = new UIGate(null, null);
  rentGate      = new UIGate(null, null);
  getChangeGate = new UIGate(null, null);
        

Als Parameter werden ein FormSheet und ein MenuSheet erwartet, die hier aber nicht benötigt werden. Deshalb wird zweimal null übergeben. Mehr kann an dieser Stelle für die Gates nicht getan werden, da deren anzuzeigende FormSheets vom Ergebnis der vorherigen Gates abhängig sind.

Der derzeit gültige DataBasket wird wie folgt ermittelt:


  final DataBasket db = getBasket();
        

Nun sind alle UIGates angelegt. Das verbleibende Gate, das entscheidet, ob genug bezahlt wurde und entsprechend reagiert, kann noch nicht sofort implementiert werden. In seiner getNextTransition-Methode wird die nächste Transition zurückgegeben. In diesem Fall handelt es sich um die zum "Wechselgeld"-Gate führenden Transition, die noch nicht angelegt ist. Im folgenden werden also alle Zustandsübergänge implementiert.

Zuerst wird die Transistion toSelectionTransition unter die Initialisierungen der Variablen eingefügt:

toSelectionTransition


  toSelectionTransition = new Transition()
  {
  };
        

Wie man sieht, wird die Transition als anonyme Klasse realisiert. Dies wird auch mit allen weiteren Transitions geschehen. In der anonymen Klasse ist die perform-Methode zu implementieren:


  public Gate perform(SaleProcess pOwner, User usr)
  {
  }  
        

Diese Methode ist in einer Transition dafür verantwortlich, alle nötigen Arbeiten auszuführen, die bis zum Erreichen des nächsten Gates nötig sind. Dazu gehört auch das Vorbereiten des nächsten Gates selbst. In diesem Fall führt die Transition von der erfolgreichen Eingabe der Kundennummer (capabilityGate) zu dem Gate, an dem die Auswahl der Videos erfolgen soll.

Jetzt füllen wir die Methode perform() mit Leben. Dazu wird zuerst das Betreten des selectionGate vorbereitet. Dafür muß der zu verwendende Bestand der Videos ermittelt werden:


  CountingStockImpl cs =
    (CountingStockImpl)Shop.getTheShop().getStock("Video-Countingstock"); 
        

Nun wird das FormSheet erzeugt, das beim Betreten des selectionGate angezeigt werden soll:


  TwoTableFormSheet ttfs =
    TwoTableFormSheet.create("Make your Selection",
			     cs,
                             db,
                             selectionGate,
                             null,
                             null,
                             false,
                             new OfferTED(true),
                             null,
                             null);
        

Wie man sieht, besitzt auch TwoTableFormSheet eine Reihe von create-Methoden für viele Kombinationen von Parametern. An dieser Stelle wurde die Methode mit folgenden Parametern verwendet:

  • Titel des FormSheets
  • Quelle, hier der CountingStock
  • Ziel, hier der DataBasket
  • UIGate, an dem das FormSheet darzustellen ist
  • Comparator für die Quelltabelle
  • Comparator für die Zieltabelle
  • Aussage, ob die Zeilen mit dem Wert 0 in der Quelltabelle gezeigt werden sollen
  • TableEntryDescriptor der Quelltabelle
  • TableEntryDescriptor der Zieltabelle
  • Strategie für die Bewegung der Einträge

Als nächstes muß das FormSheet den Bedürfnissen des selectionGate angepaßt werden. Dazu wird hinter der Erzeugung des FormSheets ein FormSheetContentCreator eingefügt. Auch er wird als anonyme Klasse implementiert:


  ttfs.addContentCreator(new FormSheetContentCreator()
  {
  });
        

Im ContentCreator muß nun eine Methode createFormSheetContent implementiert werden. Sie wird bei der Erstellung des FormSheets aufgerufen, um die notwendigen Anpassungen vorzunehmen.


  protected void createFormSheetContent(FormSheet fs)
  {
  }
        

Zunächst werden alle eventuell vorhandenen Buttons entfernt:


  fs.removeAllButtons();
        

Dann werden zwei Buttons eingefügt. Deren Betätigung löst jeweils eine neue Transitions aus. Nach dem Betätigen des "Ok"-Buttons gilt die Auswahl der Videos als beendet und der Kunde muß seine Videos bezahlen. Nach Auswahl des "Cancel"-Buttons wird der Ausleihvorgang abgebrochen.


  fs.addButton("Ok", 100, new sale.Action()
  {
    public void doAction(SaleProcess p, SalesPoint sp)
    {
      selectionGate.setNextTransition(toPayingTransition);      
    }
  });

  fs.addButton("Cancel", 101, new sale.Action()
  {
    public void doAction(SaleProcess p, SalesPoint sp)
    {
      selectionGate.setNextTransition(
        GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
    }
  });           
        

Das besondere am "Cancel"-Button ist, daß er zum Rollback-Gate führt. An diesem werden die bereits ausgewählten und sich im Datenkorb befindlichen Videos wieder in den Bestand des Verleihs einsortiert. Eine zum Rollback-Gate führende Transition ist bereits als Konstante CHANGE_TO_ROLLBACK_GATE in GateChangeTransition gespeichert.

Rollback

Die Anpassungen des FormSheets sind beendet, der FormSheetContentCreator ist nun vollständig.

Am Ende der perform-Methode muß jetzt noch das gerade erstellte FormSheet am richtigen Gate gesetzt und das nächste Gate zurückgegeben werden.


  selectionGate.setFormSheet(ttfs);
  return selectionGate;       
        

Die zum Auswählen führende Transition ist nun abgeschlossen. Als nächstes wird die toPayingTransition benötigt, die nach der Auswahl der Videos zum rentGate führt. Dort wird errechnet, wieviel Geld der Kunde in den Münzschacht des Automaten werfen muß, um sich alle gewünschten Videos ausleihen zu können.

Wie schon bei der ersten Transition wird auch hier die Methode perform implementiert, die das nächste Gate vorbereitet:

toPayingTransition


  toPayingTransition = new Transition()
  {
    public Gate perform(SaleProcess pOwner, User usr)
    {
    }
  };
        

In der perform-Methode muß zunächst der zu zahlende Betrag ausgerechnet und toPayValue zugewiesen werden. Dazu wird die Methode sumBasket benutzt, die im Interface DataBasket definiert ist. Sie benötigt als Parameter ein Objekt vom Typ DataBasketCondition, ein Objekt vom Typ BasketEntryValue und einen Initialwert vom Typ Value, auf den der Inhalt des Datenkorbes aufsummiert wird.

Das Interface DataBasketCondition definiert eine Beschreibung eines DataBasketEntry. Sie wird für Filterzwecke benutzt. In diesem Fall soll eine DataBasketCondition benutzt werden, die alle im DataBasket vorhandenen StockItems umfaßt. Ein derartiges Objekt wird mit ALL_STOCK_ITEMS bereits als Konstante in DataBasketConditionImpl, einer einfachen Implementierung von DataBasketCondition, zur Verfügung gestellt.

BasketEntryValue ist ein Hilfsinterface, das lediglich die Methode getEntryValue enthält. Diese erwartet als Parameter einen DataBasketEntry und gibt dessen Wert zurück. Auch für dieses Interface existieren in BasketEntryValue bereits Konstanten, die häufige Anwendungsfälle abdecken. In diesem Fall wird ONLY_STOCK_ITEMS verwendet, ein BasketEntryValue, der nur StockItems verarbeiten kann.

Das Interface Value ist bereits bekannt. In diesem Programm werden Katalogeinträge verwendet, die QuoteValues zum Speichern der Werte benutzen. Es muß also ein Initialwert von diesem Typ angelegt werden. Die Komponenten von QuoteValues sind IntegerValues.

Um die im Datenkorb enthaltenen StockItems aufzusummieren, wird folgender Code in die perform-Methode eingefügt:


  final DataBasketCondition dbc =
    DataBasketConditionImpl.ALL_STOCK_ITEMS;
  BasketEntryValue bev = BasketEntryValues.ONLY_STOCK_ITEMS;
  QuoteValue qvSum = new QuoteValue(new IntegerValue(0),
                                    new IntegerValue(0));

  pOwner.getBasket().sumBasket(dbc, bev, qvSum);
        

Nun muß aus der Summe der zu zahlende Betrag extrahiert werden. Ein QuoteValue besteht aus Einkaufs- und Verkaufspreis. In qvSum befinden sich somit die Summen der entsprechenden Werte in den Datenkorbeinträgen. Die Methode getBid liefert die Summe der Verkaufspreise. Es ist also einzufügen:


  toPayValue = (IntegerValue)qvSum.getBid();  
        

Damit ist der zu zahlende Betrag ermittelt. Mit dessen Hilfe kann nun das FormSheet für das nächste Gate aufgebaut werden. Dazu wird erneut eine TextInputForm verwendet.


  FormSheet tif = new TextInputForm(
    "Paying",
    "You have to pay" + myCurrency.toString(toPayValue) + ".", 
    myCurrency.toString(toPayValue));
        

Leider besitzt das FormSheet nur einen "OK"-Button, und auch dieser schließt zur Zeit lediglich das FormSheet, ohne weitere Aktionen auszuführen. Es ist also notwendig, einen FormSheetContentCreator an das FormSheet zu übergeben, der in seiner createFormSheetContent-Methode die nötigen Änderungen vornimmt.


  tif.addContentCreator(new FormSheetContentCreator()
  {
    protected void createFormSheetContent(FormSheet fs)
    {
    }
  });          
        

Da in den, an die Buttons angehängten, Aktionen auf das FormSheet zugegriffen werden muß, jedoch aus anonymen Klassen heraus nur als final erstellte Variablen des umschließenden Blockes verwendet werden können, wird in createFormSheetContent zunächst eine derartige Variable angelegt und in ihr das als Paramter übergebene FormSheet zugewiesen:


  final TextInputForm tifFinal = (TextInputForm)fs;
        

Außerdem werden alle Buttons entfernt:


  fs.removeAllButtons();
        

Nun wird ein neuer "Ok"-Button eingefügt -- Action wird wie üblich als anonyme Klasse implementiert:


  fs.addButton("Ok", 100, new sale.Action()
  {
  });
        

Die nach dem Betätigen des Buttons auszuführende Aktion besteht darin, sich die Eingabe als bezahlten Betrag zu merken und die nächste Transition zu setzen. Bei der Verarbeitung des eingegebenen Textes könnte eine ParseException (aus dem Paket java.text) auftreten, daher werden die Anweisungen in einem try-Block eingeschlossen und die Exception mit einem catch abgefangen. Im try-Block werden die Videos in den Bestand des Kunden übernommen, d.h. in seinem Kundenkonto mit ihrem Ausleihdatum vermerkt. In die Action wird folgende doAction-Methode eingefügt:


  public void doAction(SaleProcess p, SalesPoint sp)
  {
    try {
      paidValue = (IntegerValue)myCurrency.parse(tifFinal.getText());
      
      Object date = Shop.getTheShop().getTimer().getTime();
      Iterator i = db.iterator(dbc);

      while (i.hasNext()) {
        CountingStockItemDBEntry cassetteItem =
          (CountingStockItemDBEntry)i.next();
        int number = cassetteItem.count();

        for (; number-- > 0;)
          customer.addVideoCassette(
            new CassetteStoringStockItem(
              cassetteItem.getSecondaryKey(),
              date));  
      }
      rentGate.setNextTransition(toDecisionTransition);
    }
    catch (ParseException pexc) {
    }
  }
        

Um nun die ParseException korrekt zu behandeln, soll eine Meldung ausgegeben werden. Dazu wird eine MsgForm mit dem entsprechenden Text erzeugt und mit Hilfe der popUpFormSheet-Methode des ProcessContextes angezeigt. Nun erzeugt jedoch unter Umständen auch popUpFormSheet eine Exception. Diese wird hier ignoriert. Der schlimmste Fall, der eintreten kann, ist daß das MsgForm so schnell wieder verschwindet, das die Meldung nicht gelesen werden konnte. In diesem Fall wird der Benutzer entweder allein die richtige Schlußfolgerung ziehen und das Format des von ihm eingegebenen Textes korrigieren, oder aber er wird nochmal auf "Ok" drücken und die Fehlermeldung doch noch erhalten. Es wird in den catch-Block folgendes eingefügt:


  MsgForm mf = new MsgForm("Error",
                           "The specific amount does not have " +
                           "an appropriate format.");

  try {
    p.getContext().popUpFormSheet(p, mf);
  }
  catch(InterruptedException iexc) {
  }
        

Damit ist ein neuer "Ok"-Button mit angemessener Aktion eingefügt. Es folgt ein "Back"-Button, der einfach eine neue GateChangeTransition als nächste Transition setzt. Diese führt ohne weitere Aktion zurück zur Auswahl:


  fs.addButton("Back", 101, new sale.Action()
  {
    public void doAction(SaleProcess s, SalesPoint sp)
    {
      rentGate.setNextTransition(
        new GateChangeTransition(selectionGate));
    }
  });
        

Ähnlich einfach gestaltet sich der "Cancel"-Button. Der einzige Unterschied ist, daß er natürlich nicht zur Auswahl zurückführt, sondern zum Rollback-Gate.


  fs.addButton("Cancel", 102, new sale.Action()
  {
    public void doAction(SaleProcess s, SalesPoint sp)
    {
      rentGate.setNextTransition(
        GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
    }
  });   
       

Die Anpassung des FormSheets ist nun beendet, der FormSheetContentCreator vollständig.

Am Ende der perform-Methode muß das gerade erstellte FormSheet am richtigen Gate gesetzt und das nächste Gate zurückgegeben werden.


  rentGate.setFormSheet(tif);
  return rentGate;
        

Der Kunde konnte nun am rentGate seine Videos bezahlen. Die setupMachine-Methode wird um eine weitere Transition ergänzt. Diese führt vom rentGate zum decisionGate. An diesem wird entschieden, ob der Kunde zuwenig, zuviel oder genau die geforderte Summe in den Münzschacht geworfen hat. Dementsprechend werden unterschiedliche Aktionen ausgeführt.

Die toDecisionTransition wird wie folgt erstellt:

toDecisionTransition


  toDecisionTransition = new Transition()
  {
    public Gate perform(SaleProcess pOwner, User usr)
    {
    }
  };
        

Die Überprüfung des Geldes erfolgt mit Hilfe von if-Anweisungen innerhalb der perform-Methode. Außerdem wird der zu zahlende Betrag zum Geldbestand des Videoautomaten hinzugefügt, wenn ausreichend Geld bezahlt wurde:


  if (paidValue.compareTo(toPayValue) >= 0) {
    if (toPayValue.getValue().intValue() > 0)
      ((CountingStock)Shop.getTheShop().getStock(
        "coin slot")).add("1-Pfennig-Stueck", 
                          toPayValue.getValue().intValue(), 
                          pOwner.getBasket());
    if (paidValue.compareTo(toPayValue) == 0)
      payAssessment = 0;
    else
      payAssessment = 1;
  }
  else
    payAssessment = -1; 
        

Wurde genau der gewünschte Betrag eingeworfen, wird payAssessment auf 0 gesetzt, wurde zu viel bezahlt auf 1 und wurde zu wenig bezahlt auf -1.

Am Ende der perform-Methode muß noch das decisionGate als nächstes zu betretendes Gate gesetzt werden:


  return decisionGate;
        

Das decisionGate wird im folgenden implementiert:

decisionGate


  decisionGate = new Gate()
  {
  };
        

Im decisionGate steht die getNextTransition-Methode, die entscheidet, welche Transition bei welchem Ereignis ausgelöst wird.


  public Transition getNextTransition(SaleProcess pOwner, User usr)
    throws InterruptedException 
  {
  }
        

Sollte der Kunde zu wenig Geld in den Münzschacht des Automaten eingeworfen haben, wird er erneut zum Bezahlen aufgefordert. Wurde genau der zu zahlende Betrag gegeben, wird das Ausleihen beendet und das Commit-Gate erreicht. Erwartet der Kunde Wechselgeld, so führt die toGetChangeTransition zum getChangeGate. Sollte der eigentlich unmögliche Fall eintreten, daß die Variable payAssessment mit keinem der drei Werte belegt ist, tritt das default-Ereignis ein. Es werden alle laufenden Prozesse beendet und die Anwendung springt zum Quit-Gate.

Die Auswahl der jeweils auszuführenden Aktion erfolgt mit Hilfe einer switch-Anweisung in der getNextTransition-Methode:


  switch(payAssessment) {
    case -1: 
      FormSheet mf = new MsgForm("Error", 
                                 "You have to pay more!", 
                                 false);
      pOwner.getContext().popUpFormSheet(pOwner, mf);
      return new GateChangeTransition(rentGate);

    case 0:
      return GateChangeTransition.CHANGE_TO_COMMIT_GATE;

    case 1:
      return toGetChangeTransition;

    default:
      FormSheet mf2 = new MsgForm("Error", 
                                  "Internal error at Decision Gate. "
                                  + "Will quit process.", 
                                  false);
      pOwner.getContext().popUpFormSheet(pOwner, mf2);
      return GateChangeTransition.CHANGE_TO_QUIT_GATE;
  }
        

Als letztes wird die noch fehlende toGetChangeTransition implementiert. Zuerst wird wieder eine Transition angelegt und in ihr die perform-Methode implementiert:


  toGetChangeTransition = new Transition()
  {
    public Gate perform(SaleProcess pOwner, User usr)
    {
    } 
  };        
        

In der dieser Methode wird die Oberfläche vorbereitet, die beim Erreichen des neuen Gates dargestellt werden soll. Es wird ein einfaches MsgForm erzeugt, das den zurückgegebenen Betrag anzeigt.


  MsgForm mf = new MsgForm(
    "GetChange", 
    "You get "
      + myCurrency.toString(
        (IntegerValue)paidValue.subtract(toPayValue))
      + " Change.");
        

Auch hier besitzt das FormSheet nur einen "OK"-Button, der lediglich das Fenster schließt. Es ist also wieder notwendig einen FormSheetContentCreator an das FormSheet zu übergeben, der in seiner createFormSheetContent-Methode dem Button eine neue Aktion zuordnet. Mit der getButton-Methode wird der Button aus dem FormSheet geholt, und die setAction-Methode ändert die Aktion, die vom Buttons ausgelöst wird. In unserer Anwendung wird zum Commit-Gate gesprungen, da der Ausleihvorgang mit Rückgabe des Wechselgeldes abgeschlossen ist.


  mf.addContentCreator(new FormSheetContentCreator()
  {
    protected void createFormSheetContent(FormSheet fs)
    {
      fs.getButton(FormSheet.BTNID_OK).setAction(new Action()
      {
       	public void doAction (SaleProcess s, SalesPoint sp)
	{
          getChangeGate.setNextTransition(
            GateChangeTransition.CHANGE_TO_COMMIT_GATE);
        }
      });
    }
  });
        

Die perform-Methode ist fertig implementiert. Nach ihr wird das betreffende FormSheet am zu betretenden getChangeGate gesetzt, und das Gate, daß als nächstes zu betreten ist, wird zurückgegeben:


  getChangeGate.setFormSheet(mf);
  return getChangeGate;
        

Alle wichtigen Transistions sind fertiggestellt.

Am Anfang der setupMachine wurde eine TextInputForm erstellt, die die Eingabe der Kundennummer erwartet. Diese Eingabe muß an den gerade erstellten Automaten angebunden werden, in dem Buttons hinzugefügt werden, die bestimmte Aktionen auslösen. Ohne sie wird man die bereits implementierten Transitions nie erreichen.

InitialGate

Am Ende der setupMachine-Methode wird der schon bekannte FormSheetContentCreator erstellt:


  tif.addContentCreator(new FormSheetContentCreator() 
  {
    protected void createFormSheetContent(FormSheet fs)
    {
    }
  });  
        

In der createFormSheetContent-Methode werden zuerst alle Buttons entfernt:


  fs.removeAllButtons();
        

Nun wird ein neuer "Ok"-Button eingefügt, der Action als anonyme Klasse implementieren:


  fs.addButton("Ok", 100, new sale.Action()
  {
  });          
       

Die nach dem Betätigen des Buttons auszuführende Aktion besteht darin, bei falscher Kundennummer die Eingabe wiederholen zu lassen. Ist die Kundennummer jedoch ein Integer, wird ggf. ein neuer Kunde erzeugt und dann zum selectionGate gewechselt. In die Action wird folgende doAction-Methode eingefügt:


  public void doAction(SaleProcess p, SalesPoint sp)
  {
    String customerID      = tif.getText();
    boolean isNotAnInteger = true;
 
    try {
      new Integer(customerID);
      isNotAnInteger = false;
    }
    catch(NumberFormatException nfe) {
      isNotAnInteger = true;
    }

    if(!isNotAnInteger
       && (new Integer(customerID)).intValue() > 0) {
      try {
        customer = new Customer(customerID);
        VideoMachine.addCustomer(customer);
      }
      catch (DuplicateKeyException dke) {
        customer = VideoMachine.getCustomerByID(customerID); 
      }
      capabilityGate.setNextTransition(toSelectionTransition);
    }
    else {
      JOptionPane.showMessageDialog(
        null,
        "CustomerID must be a positive Number!");
      capabilityGate.setNextTransition(
        new GateChangeTransition(capabilityGate));
    }
  }  
        

Für JOptionPane.showMessageDialog() ist die folgende import-Anweisung nötig:


  import javax.swing.JOptionPane;
        

Es fehlt nun noch ein "Cancel"-Button, der zum Rollback-Gate führt:


  fs.addButton("Cancel", 101, new sale.Action()
  {
    public void doAction(SaleProcess s, SalesPoint sp)
    {
      capabilityGate.setNextTransition(
        GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
    }
  });
        

Der FormSheetContentCreator ist abgeschlossen und das FormSheet wird am entsprechenden Gate am Ende der setupMachine-Methode gesetzt.


  capabilityGate.setFormSheet(tif);
        

Die setupMachine-Methode ist damit abgeschlossen. Als letztes muß die Methode getInitialGate implementiert werden, die das Start-Gate des RentProcess liefert und ihn in Gang setzt:


  public Gate getInitialGate()
  {
    setupMachine();
    return capabilityGate;
  }      
        

Um den Prozeß in die Anwendung einzubinden wird in der DefaultCounterFormCreator-Klasse die doAction-Methode des "rent"-Buttons mit einer Zeile Code ausgestattet:


  s.runProcess(new RentProcess());
        

Viel Spaß beim Ausleihen der Videos!!

Hier der Quelltext der in diesem Kapitel geänderten Klassen:

Der Kunde Die Rückgabe des Videos

last modified on 08.04.2002
by ch17