(* Ulm's Oberon Library
   Copyright (C) 1989-1995 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: Forwarders.om,v 1.1 1996/01/04 16:40:57 borchert Exp $
   ----------------------------------------------------------------------------
   $Log: Forwarders.om,v $
   Revision 1.1  1996/01/04  16:40:57  borchert
   Initial revision

   ----------------------------------------------------------------------------
*)

MODULE ulmForwarders; (* AFB 3/95 *)

   IMPORT Disciplines := ulmDisciplines, Events := ulmEvents, Resources := ulmResources, Services := ulmServices, SYSTEM; 
   (* SYSTEM is necessary to cast to Disciplines.Discipline; noch *)

   TYPE
      Object* = Services.Object;
      ForwardProc* = PROCEDURE (from, to: Object);

   TYPE
      ListOfForwarders = POINTER TO ListOfForwardersRec;
      ListOfForwardersRec =
	 RECORD
	    forward: ForwardProc;
	    next: ListOfForwarders;
	 END;
      ListOfDependants = POINTER TO ListOfDependantsRec;
      ListOfDependantsRec =
	 RECORD
	    dependant: Object;
	    next: ListOfDependants;
	 END;
      TypeDiscipline = POINTER TO TypeDisciplineRec;
      TypeDisciplineRec =
	 RECORD
	    (Disciplines.DisciplineRec)
	    list: ListOfForwarders;
	 END;
      ObjectDiscipline = POINTER TO ObjectDisciplineRec;
      ObjectDisciplineRec =
	 RECORD
	    (Disciplines.DisciplineRec)
	    dependants: ListOfDependants;
	    forwarders: ListOfForwarders;
	    dependsOn: Object;
	 END;
   VAR
      genlist: ListOfForwarders; (* list which applies to all types *)
      typeDiscID: Disciplines.Identifier;
      objectDiscID: Disciplines.Identifier;

   (* === private procedures ============================================ *)

   PROCEDURE RemoveDependant(VAR list: ListOfDependants; dependant: Object);
      VAR
	 prev, p: ListOfDependants;
   BEGIN
      prev := NIL; p := list;
      WHILE (p # NIL) & (p.dependant # 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);
      (* remove list of dependants in case of termination and
	 remove event.resource from the list of dependants of that
	 object it depends on
      *)
      VAR
	 odisc: ObjectDiscipline;
	 dependsOn: Object;
   BEGIN
      WITH event: Resources.Event DO
	 IF event.change = Resources.terminated THEN
	    IF Disciplines.Seek(event.resource, objectDiscID, SYSTEM.VAL(Disciplines.Discipline, odisc)) THEN (* noch *)
	       Disciplines.Remove(event.resource, objectDiscID);
	       dependsOn := odisc.dependsOn;
	       IF (dependsOn # NIL) & ~Resources.Terminated(dependsOn) &
		     Disciplines.Seek(dependsOn, objectDiscID, SYSTEM.VAL(Disciplines.Discipline, odisc)) THEN (* noch *)
		  RemoveDependant(odisc.dependants, event.resource(Object));
	       END;
	    END;
	 END;
      END;
   END TerminationHandler;

   PROCEDURE Insert(VAR list: ListOfForwarders; forward: ForwardProc);
      VAR
	 member: ListOfForwarders;
   BEGIN
      NEW(member); member.forward := forward;
      member.next := list; list := member;
   END Insert;

   PROCEDURE GetObjectDiscipline(object: Object; VAR odisc: ObjectDiscipline);
      VAR
	 resourceNotification: Events.EventType;
   BEGIN
      IF ~Disciplines.Seek(object, objectDiscID, SYSTEM.VAL(Disciplines.Discipline, odisc)) THEN (* noch *)
	 NEW(odisc); odisc.id := objectDiscID; odisc.dependants := NIL;
	 odisc.forwarders := NIL; odisc.dependsOn := NIL;
	 (* let's state our interest in termination of `object' if
	    we see this object the first time
	 *)
	 Resources.TakeInterest(object, resourceNotification);
	 Events.Handler(resourceNotification, TerminationHandler);
	 Disciplines.Add(object, odisc);
      END;
   END GetObjectDiscipline;

   (* === exported procedures =========================================== *)

   PROCEDURE Register*(for: ARRAY OF CHAR; forward: ForwardProc);
      (* register a forwarder which is to be called for all
	 forward operations which affects extensions of `for';
	 "" may be given for Services.Object
      *)

      VAR
	 type: Services.Type;
	 tdisc: TypeDiscipline;

   BEGIN (* Register *)
      IF for = "" THEN
	 Insert(genlist, forward);
      ELSE
	 Services.SeekType(for, type);
	 ASSERT(type # NIL);
	 IF ~Disciplines.Seek(type, typeDiscID, SYSTEM.VAL(Disciplines.Discipline, tdisc)) THEN
	    NEW(tdisc); tdisc.id := typeDiscID; tdisc.list := NIL;
	 END;
	 Insert(tdisc.list, forward);
	 Disciplines.Add(type, tdisc);
      END;
   END Register;

   PROCEDURE RegisterObject*(object: Object; forward: ForwardProc);
      (* to be called instead of Register if specific objects
	 are supported only and not all extensions of a type
      *)
      VAR
	 odisc: ObjectDiscipline;
   BEGIN
      GetObjectDiscipline(object, odisc);
      Insert(odisc.forwarders, forward);
   END RegisterObject;

   PROCEDURE Update*(object: Object; forward: ForwardProc);
      (* is to be called by one of the registered forwarders if
	 an interface for object has been newly installed or changed
	 in a way which needs forward to be called for each of
	 the filter objects which delegate to `object'
      *)
      VAR
	 odisc: ObjectDiscipline;
	 client: ListOfDependants;
   BEGIN
      IF Disciplines.Seek(object, objectDiscID, SYSTEM.VAL(Disciplines.Discipline, odisc)) THEN (* noch *)
	 client := odisc.dependants;
	 WHILE client # NIL DO
	    forward(client.dependant, object);
	    client := client.next;
	 END;
      END;
   END Update;

   PROCEDURE Forward*(from, to: Object);
      (* forward (as far as supported) all operations from `from' to `to' *)
      VAR
	 type, otherType, baseType: Services.Type;
	 tdisc: TypeDiscipline;
	 odisc: ObjectDiscipline;
	 client: ListOfDependants;
	 forwarder: ListOfForwarders;

      PROCEDURE CallForwarders(list: ListOfForwarders);
      BEGIN
	 WHILE list # NIL DO
	    list.forward(from, to);
	    list := list.next;
	 END;
      END CallForwarders;

   BEGIN (* Forward *)
      Services.GetType(from, type);
      Services.GetType(to, otherType);
      ASSERT((type # NIL) & (otherType # NIL));

      IF Resources.Terminated(to) OR Resources.Terminated(from) THEN
	 (* forwarding operations is no longer useful *)
	 RETURN
      END;
      Resources.DependsOn(from, to);

      (* update the list of dependants for `to' *)
      GetObjectDiscipline(to, odisc);
      NEW(client); client.dependant := from;
      client.next := odisc.dependants; odisc.dependants := client;

      (* call object-specific forwarders *)
      CallForwarders(odisc.forwarders);

      LOOP (* go through the list of base types in descending order *)
	 IF Disciplines.Seek(type, typeDiscID, SYSTEM.VAL(Disciplines.Discipline, tdisc)) & (* noch *)
	       Services.IsExtensionOf(otherType, type) THEN
	    CallForwarders(tdisc.list);
	 END;
	 Services.GetBaseType(type, baseType);
	 IF baseType = NIL THEN EXIT END;
	 type := baseType;
      END;
      CallForwarders(genlist);
   END Forward;

BEGIN
   genlist := NIL;
   typeDiscID := Disciplines.Unique();
   objectDiscID := Disciplines.Unique();
END ulmForwarders.
