(* Ulm's Oberon Library
   Copyright (C) 1989-1994 by University of Ulm, SAI, D-89069 Ulm, Germany
   ----------------------------------------------------------------------------
   Ulm's Oberon Library is free software; you can redistribute it
   and/or modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either version
   2 of the License, or (at your option) any later version.

   Ulm's Oberon Library is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   ----------------------------------------------------------------------------
   E-mail contact: oberon@mathematik.uni-ulm.de
   ----------------------------------------------------------------------------
   $Id: RelatedEven.om,v 1.8 2005/04/28 08:30:09 borchert Exp $
   ----------------------------------------------------------------------------
   $Log: RelatedEven.om,v $
   Revision 1.8  2005/04/28 08:30:09  borchert
   added assertion to Forward that takes care that from # to
   (otherwise we get a nasty infinite loop)

   Revision 1.7  2004/09/09 21:04:24  borchert
   undoing change of Revision 1.5:
      fields dependants and dependson must not be subject of
      Save/Restore as this makes it impossible to undo the
      dependencies within the TerminationHandler
   we no longer remove the discipline in case of terminated
   objects as this causes a list of error events to be lost

   Revision 1.6  2004/02/18 17:01:59  borchert
   Raise asserts now that event.type # NIL

   Revision 1.5  2004/02/18 16:53:48  borchert
   fields dependants and dependson moved from discipline to state
   object to support them for Save/Restore

   Revision 1.4  1998/01/12  14:39:18  borchert
   some bug fixes around RelatedEvents.null

   Revision 1.3  1995/03/20  17:05:13  borchert
   - Save & Restore added
   - support for Forwarders & Resources added

   Revision 1.2  1994/08/27  14:49:44  borchert
   null object added

   Revision 1.1  1994/02/22  20:09:53  borchert
   Initial revision

   ----------------------------------------------------------------------------
   AFB 11/91
   ----------------------------------------------------------------------------
*)

MODULE ulmRelatedEvents;

   (* relate events to objects *)

   IMPORT Disciplines := ulmDisciplines, Events := ulmEvents, Forwarders := ulmForwarders, Objects := ulmObjects, Priorities := ulmPriorities, Resources := ulmResources, SYSTEM;

   CONST
      (* possible directions of propagated events *)
      forward = 0;   (* forward along the forwardTo chain, if given *)
      backward = 1;  (* forward event to all dependants, if present *)
      both = 2;      (* forward event to both directions *)
   TYPE
      Direction = SHORTINT; (* forward, backward, both *)

   TYPE
      Object* = Disciplines.Object;
      Event* = POINTER TO EventRec;
      EventRec* =
	 RECORD
	    (Events.EventRec)
	    object*: Object;
	    event*: Events.Event;
	 END;
      Queue = POINTER TO QueueRec;
      QueueRec* =
	 RECORD
	    (Objects.ObjectRec)
	    event*: Events.Event;
	    next*: Queue;
	 END;
      ObjectList = POINTER TO ObjectListRec;
      ObjectListRec =
	 RECORD
	    object: Object;
	    next: ObjectList;
	 END;

   TYPE
      State = POINTER TO StateRec;
      StateRec =
	 RECORD
	    default: BOOLEAN; (* default reaction? *)
	    eventType: Events.EventType; (* may be NIL *)
	    queue: BOOLEAN; (* are events to be queued? *)
	    forwardto: Object;
	    head, tail: Queue;
	    saved: State;
	 END;
      Discipline = POINTER TO DisciplineRec;
      DisciplineRec =
	 RECORD
	    (Disciplines.DisciplineRec)
	    state: State;
	    dependants: ObjectList;
	    dependsOn: Object;
	 END;
   VAR
      id: Disciplines.Identifier;
   VAR
      null*: Object; (* object which ignores all related events *)
      nullevent: Events.EventType;

   PROCEDURE RemoveDependant(VAR list: ObjectList; dependant: Object);
      VAR
	 prev, p: ObjectList;
   BEGIN
      prev := NIL; p := list;
      WHILE (p # NIL) & (p.object # dependant) DO
	 prev := p; p := p.next;
      END;
      IF p # NIL THEN
	 IF prev = NIL THEN
	    list := p.next;
	 ELSE
	    prev.next := p.next;
	 END;
      END;
   END RemoveDependant;

   PROCEDURE TerminationHandler(event: Events.Event);
      VAR
	 disc: Discipline;
   BEGIN
      WITH event: Resources.Event DO
	 IF (event.change = Resources.terminated) &
	       Disciplines.Seek(event.resource, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	    IF (disc.dependsOn # NIL) &
                  Disciplines.Seek(disc.dependsOn, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	       RemoveDependant(disc.dependants, event.resource);
	       disc.dependsOn := NIL;
	    END;
	    (*
	       afb 9/2004:
	       do not remove this discipline for dead objects
	       as this makes it impossible to retrieve the final
	       list of error events
	       Disciplines.Remove(event.resource, id);
	    *)
	 END;
      END;
   END TerminationHandler;

   PROCEDURE CreateState(VAR state: State);
   BEGIN
      NEW(state);
      state.eventType := NIL;
      state.queue := FALSE; state.head := NIL; state.tail := NIL;
      state.forwardto := NIL;
      state.default := TRUE;
      state.saved := NIL;
   END CreateState;

   PROCEDURE CreateDiscipline(VAR disc: Discipline);
   BEGIN
      NEW(disc); disc.id := id; CreateState(disc.state);
   END CreateDiscipline;

   PROCEDURE GetEventType*(object: Object; VAR eventType: Events.EventType);
      (* returns an event type for the given object;
	 all events related to the object are also handled by this event type
      *)
      VAR
	 disc: Discipline;
	 state: State;
   BEGIN
      IF object = null THEN
	 eventType := nullevent;
      ELSE
	 IF ~Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	    CreateDiscipline(disc);
	    Disciplines.Add(object, disc); 
	 END;
	 state := disc.state;
	 state.default := FALSE;
	 IF state.eventType = NIL THEN
	    Events.Define(state.eventType);
	    Events.SetPriority(state.eventType, Priorities.liberrors + 1);
	    Events.Ignore(state.eventType);
	 END;
	 eventType := state.eventType;
      END;
   END GetEventType;

   PROCEDURE Forward*(from, to: Object);
      (* causes all events related to `from' to be forwarded to `to' *)
      VAR
	 disc: Discipline;
   BEGIN
      IF (from # NIL) & (from # null) THEN
	 ASSERT(from # to);
	 IF ~Disciplines.Seek(from, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	    CreateDiscipline(disc);
	    Disciplines.Add(from, disc);
	 END;
	 IF to = null THEN
	    to := NIL;
	 END;
	 disc.state.forwardto := to;
	 disc.state.default := FALSE;
      END;
   END Forward;

   PROCEDURE ForwardToDependants(from, to: Forwarders.Object);
      (* is called by Forwarders.Forward:
	 build a backward chain from `to' to `from'
      *)
      VAR
	 fromDisc, toDisc: Discipline;
	 member: ObjectList;
	 eventType: Events.EventType;
   BEGIN
      IF (from = null) OR (to = null) THEN RETURN END;
      IF ~Disciplines.Seek(from, id, SYSTEM.VAL(Disciplines.Discipline, fromDisc)) THEN (* noch *)
	 CreateDiscipline(fromDisc); Disciplines.Add(from, fromDisc);
      END;
      IF fromDisc.dependsOn # NIL THEN RETURN END;
      fromDisc.dependsOn := to;
      Resources.TakeInterest(from, eventType);
      Events.Handler(eventType, TerminationHandler);

      IF ~Disciplines.Seek(to, id, SYSTEM.VAL(Disciplines.Discipline, toDisc)) THEN (* noch *)
	 CreateDiscipline(toDisc); Disciplines.Add(to, toDisc);
      END;
      NEW(member); member.object := from;
      member.next := toDisc.dependants; toDisc.dependants := member;
   END ForwardToDependants;

   PROCEDURE QueueEvents*(object: Object);
      (* put all incoming events into a queue *)
      VAR
	 disc: Discipline;
	 state: State;
   BEGIN
      IF (object # NIL) & (object # null) THEN
	 IF ~Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN
	    CreateDiscipline(disc);
	    Disciplines.Add(object, disc);
	 END;
	 state := disc.state;
	 state.default := FALSE;
	 IF ~state.queue THEN
	    state.queue := TRUE; state.head := NIL; state.tail := NIL;
	 END;
      END;
   END QueueEvents;

   PROCEDURE GetQueue*(object: Object; VAR queue: Queue);
      (* return queue of related events which is removed
	 from the object;
	 object must have been prepared by QueueEvents
      *)
      VAR
	 disc: Discipline;
	 state: State;
   BEGIN
      IF (object # NIL) & (object # null) &
	    Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) & disc.state.queue THEN (* noch *)
	 state := disc.state;
	 queue := state.head; state.head := NIL; state.tail := NIL;
      ELSE
	 queue := NIL;
      END;
   END GetQueue;

   PROCEDURE EventsPending*(object: Object) : BOOLEAN;
      (* return TRUE if GetQueue will return a queue # NIL *)
      VAR
	 disc: Discipline;
   BEGIN
      IF (object # NIL) & (object # null) &
	    Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) & disc.state.queue THEN (* noch *)
	 RETURN disc.state.head # NIL
      ELSE
	 RETURN FALSE
      END;
   END EventsPending;

   PROCEDURE Reset*(object: Object);
      (* return to default behaviour *)
      VAR
	 disc: Discipline;
	 state: State;
   BEGIN
      IF object # null THEN
	 IF Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	    IF (disc.state.saved = NIL) &
		  (disc.dependsOn = NIL) &
		  (disc.dependants = NIL) THEN
	       Disciplines.Remove(object, id);
	    ELSE
	       state := disc.state;
	       state.queue := FALSE; state.head := NIL; state.tail := NIL;
	       state.eventType := NIL; state.forwardto := NIL;
	       state.default := TRUE;
	    END;
	 END;
      END;
   END Reset;

   PROCEDURE Save*(object: Object);
      (* save current status of the given object and reset to
	 default behaviour;
	 the status includes the reaction types and event queues;
	 Save operations may be nested
      *)
      VAR
	 disc: Discipline;
	 state: State;
   BEGIN
      IF object # null THEN
	 IF ~Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	    CreateDiscipline(disc);
	    Disciplines.Add(object, disc);
	 END;
	 CreateState(state);
	 state.saved := disc.state; disc.state := state;
      END;
   END Save;

   PROCEDURE Restore*(object: Object);
      (* restore status saved earlier by Save *)
      VAR
	 disc: Discipline;
   BEGIN
      IF Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) & (disc.state.saved # NIL) THEN (* noch *)
	 disc.state := disc.state.saved;
      END;
   END Restore;

   PROCEDURE InternalRaise(object: Object; dir: Direction; event: Events.Event);
      VAR
	 disc: Discipline;
	 state: State;
	 relEvent: Event;
	 element: Queue;	(* new element of queue *)
	 dependant: ObjectList;
   BEGIN
      IF (object = null) OR ~Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN RETURN END;

      (* backward chaining *)
      IF (disc.dependants # NIL) & (dir IN {backward, both}) THEN
	 dependant := disc.dependants;
	 WHILE dependant # NIL DO
	    InternalRaise(dependant.object, backward, event);
	    dependant := dependant.next;
	 END;
      END;

      (* local handling & forward chaining *)
      IF ~disc.state.default THEN
	 state := disc.state;
	 IF state.queue THEN
	    NEW(element); element.next := NIL; element.event := event;
	    IF state.tail # NIL THEN
	       state.tail.next := element;
	    ELSE
	       state.head := element;
	    END;
	    state.tail := element;
	 END;
	 IF state.eventType # NIL THEN
	    NEW(relEvent);
	    relEvent.message := event.message;
	    relEvent.type := state.eventType;
	    relEvent.object := object;
	    relEvent.event := event;
	    Events.Raise(relEvent);
	 END;
	 IF (state.forwardto # NIL) & (dir IN {both, forward}) THEN
	    InternalRaise(state.forwardto, forward, event);
	 END;
      END;
   END InternalRaise;

   PROCEDURE Raise*(object: Object; event: Events.Event);
      VAR
	 disc: Discipline;
   BEGIN
      ASSERT(event.type # NIL);
      IF object # null THEN
	 IF (object = NIL) OR ~Disciplines.Seek(object, id, SYSTEM.VAL(Disciplines.Discipline, disc)) THEN (* noch *)
	    Events.Raise(event);
	 ELSE
	    InternalRaise(object, both, event);
	 END;
      END;
   END Raise;

   PROCEDURE AppendQueue*(object: Object; queue: Queue);
      (* Raise(object, event) for all events of the queue *)
   BEGIN
      WHILE queue # NIL DO
	 Raise(object, queue.event);
	 queue := queue.next;
      END;
   END AppendQueue;

BEGIN
   id := Disciplines.Unique();
   NEW(null);
   Events.Define(nullevent);
   Forwarders.Register("", ForwardToDependants);
END ulmRelatedEvents.
