(* 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: PersistentD.om,v 1.4 1998/02/22 10:25:22 borchert Exp $
   ----------------------------------------------------------------------------
   $Log: PersistentD.om,v $
   Revision 1.4  1998/02/22  10:25:22  borchert
   bug fix in GetObject: Disciplines.Add was missing if the main object
   is just an extension of Disciplines.Object and not of
   PersistentDisciplines.Object

   Revision 1.3  1996/07/24  07:41:28  borchert
   bug fix: count component was not initialized (with the
            exception of CreateObject) -- detected by Martin Hasch

   Revision 1.2  1995/03/17  16:13:33  borchert
   - persistent disciplines may now be attached to non-persistent objects
   - some fixes due to changes of PersistentObjects

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

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

MODULE ulmPersistentDisciplines;

   IMPORT Disciplines := ulmDisciplines, Forwarders := ulmForwarders, NetIO := ulmNetIO, Objects := ulmObjects, PersistentObjects := ulmPersistentObjects,
      Services := ulmServices, Streams := ulmStreams, SYS := SYSTEM;

   CONST
      objectName = "PersistentDisciplines.Object";
      disciplineName = "PersistentDisciplines.Discipline";

   TYPE
      Identifier* = LONGINT;

      Discipline* = POINTER TO DisciplineRec;
      DisciplineRec* =
	 RECORD
	    (PersistentObjects.ObjectRec)
	    id*: Identifier; (* should be unique for all types of disciplines *)
	 END;

      DisciplineList = POINTER TO DisciplineListRec;
      DisciplineListRec =
	 RECORD
	    discipline: Discipline;
	    id: Identifier; (* copied from discipline.id *)
	    next: DisciplineList;
	 END;

      Interface = POINTER TO InterfaceRec;
      Object = POINTER TO ObjectRec;
      ObjectRec* =
	 RECORD
	    (PersistentObjects.ObjectRec)
	    (* private part *)
	    count: LONGINT; (* number of attached disciplines *)
	    list: DisciplineList; (* set of disciplines *)
	    if: Interface; (* overrides builtins if # NIL *)
	    forwardTo: Object;
	    usedBy: Object; (* used as target of UseInterfaceOf *)
	       (* very restrictive way of avoiding reference cycles:
		  forwardTo references must be built from inner to
		  outer objects and not vice versa
	       *)
	 END;

   TYPE
      VolatileDiscipline = POINTER TO VolatileDisciplineRec;
      VolatileDisciplineRec =
	 RECORD
	    (Disciplines.DisciplineRec)
	    object: Object;
	 END;
   VAR
      volDiscID: Disciplines.Identifier;

   TYPE
      AddProc* = PROCEDURE (object: Disciplines.Object; discipline: Discipline);
      RemoveProc* = PROCEDURE (object: Disciplines.Object; id: Identifier);
      SeekProc* = PROCEDURE (object: Disciplines.Object; id: Identifier;
			    VAR discipline: Discipline) : BOOLEAN;
      InterfaceRec* =
	 RECORD
	    (Objects.ObjectRec)
	    add*: AddProc;
	    remove*: RemoveProc;
	    seek*: SeekProc;
	 END;

   VAR
      unique: Identifier;
      objIf: PersistentObjects.Interface;
      objDatatype, discDatatype: Services.Type;

   CONST
      hashtabsize = 32;
   TYPE
      Sample = POINTER TO SampleRec;
      SampleRec =
	 RECORD
	    id: Identifier;
	    sample: Discipline;
	    next: Sample;
	 END;
      BucketTable = ARRAY hashtabsize OF Sample;
   VAR
      samples: BucketTable;

   PROCEDURE CreateObject*(VAR object: Object);
      (* creates a new object; this procedures should be called instead of
	 NEW for objects of type `Object'
      *)
   BEGIN
      NEW(object);
      object.count := 0; (* up to now, there are no attached disciplines *)
      object.list := NIL;
      object.if := NIL;
      PersistentObjects.Init(object, objDatatype);
   END CreateObject;

   PROCEDURE GetObject(obj: Disciplines.Object; VAR object: Object);
      VAR
	 disc: VolatileDiscipline;
   BEGIN
      IF obj IS Object THEN
	 object := obj(Object);
	 (* initialize private components now if not done already;
	    we assume here that pointers which have not been
	    initialized yet are defined to be NIL
	    (because of the garbage collection);
	    a similar assumption does not necessarily hold for
	    other types (e.g. integers)
	 *)
	 IF object.list = NIL THEN
	    object.count := 0;
	 END;
      ELSIF Disciplines.Seek(obj, volDiscID, SYS.VAL(Disciplines.Discipline, disc)) THEN
	 object := disc.object;
      ELSE
	 CreateObject(object);
	 NEW(disc); disc.id := volDiscID; disc.object := object;
	 Disciplines.Add(obj, disc);
      END;
   END GetObject;

   (* === normal stuff for disciplines ===================================== *)

   PROCEDURE Unique*(sample: Discipline) : Identifier;
      (* returns a unique identifier;
         this procedure should be called during initialization by
         all modules defining a discipline type;
	 a sample of the associated discipline has to be provided
      *)
      VAR
	 hashval: Identifier;
	 entry: Sample;
   BEGIN
      INC(unique);
      NEW(entry); entry.id := unique; entry.sample := sample;
      hashval := unique MOD hashtabsize;
      entry.next := samples[hashval]; samples[hashval] := entry;
      RETURN unique
   END Unique;

   PROCEDURE GetSample*(id: Identifier) : Discipline;
      (* return sample for the given identifier;
	 NIL will be returned if id has not yet been returned by Unique
      *)
      VAR
	 hashval: Identifier;
	 ptr: Sample;
   BEGIN
      hashval := id MOD hashtabsize;
      ptr := samples[hashval];
      WHILE (ptr # NIL) & (ptr.id # id) DO
	 ptr := ptr.next;
      END;
      IF ptr # NIL THEN
	 RETURN ptr.sample
      ELSE
	 RETURN NIL
      END;
   END GetSample;

   PROCEDURE AttachInterface*(object: Disciplines.Object; if: Interface);
      (* override the builtin implementations of Add, Remove and
	 Seek for `object' with the implementations given by `if'
      *)
      VAR
	 po: Object;
   BEGIN
      GetObject(object, po);
      IF (po.list = NIL) & (po.forwardTo = NIL) THEN
	 po.if := if;
      END;
   END AttachInterface;

   PROCEDURE UseInterfaceOf*(object, host: Disciplines.Object);
      (* forward Add, Remove and Seek operations from object to host *)
      VAR
	 po, phost: Object;
   BEGIN
      GetObject(object, po); GetObject(host, phost);
      IF (po.list = NIL) & (po.forwardTo = NIL) &
	    (po.usedBy = NIL) THEN
	 po.forwardTo := phost;
	 phost.usedBy := po; (* avoid reference cycles *)
      END;
   END UseInterfaceOf;

   PROCEDURE Forward(from, to: Forwarders.Object);
   BEGIN
      UseInterfaceOf(from, to);
   END Forward;

   PROCEDURE Remove*(object: Disciplines.Object; id: Identifier);
      (* remove the discipline with the given id from object, if it exists *)
      VAR
	 po: Object;
	 prev, dl: DisciplineList;
   BEGIN
      GetObject(object, po);
      WHILE po.forwardTo # NIL DO
	 po := po.forwardTo;
      END;
      IF po.if = NIL THEN
	 prev := NIL;
	 dl := po.list;
	 WHILE (dl # NIL) & (dl.id # id) DO
	    prev := dl; dl := dl.next;
	 END;
	 IF dl # NIL THEN
	    IF prev = NIL THEN
	       po.list := dl.next;
	    ELSE
	       prev.next := dl.next;
	    END;
	    DEC(po.count); (* discipline removed *)
	 END;
      ELSE
	 po.if.remove(po, id);
      END;
   END Remove;

   PROCEDURE Add*(object: Disciplines.Object; discipline: Discipline);
      (* adds a new discipline to the given object;
	 if already a discipline with the same identifier exist
	 it is deleted first
      *)
      VAR
	 po: Object;
	 dl: DisciplineList;
   BEGIN
      GetObject(object, po);
      WHILE po.forwardTo # NIL DO
	 po := po.forwardTo;
      END;
      IF po.if = NIL THEN
	 dl := po.list;
	 WHILE (dl # NIL) & (dl.id # discipline.id) DO
	    dl := dl.next;
	 END;
	 IF dl = NIL THEN
	    NEW(dl);
	    dl.id := discipline.id;
	    dl.next := po.list;
	    po.list := dl;
	    INC(po.count); (* discipline added *)
	 END;
	 dl.discipline := discipline;
      ELSE
	 po.if.add(po, discipline);
      END;
   END Add;

   PROCEDURE Seek*(object: Disciplines.Object; id: Identifier;
		  VAR discipline: Discipline) : BOOLEAN;
      (* returns TRUE if a discipline with the given id is found *)
      VAR
	 po: Object;
	 dl: DisciplineList;
   BEGIN
      GetObject(object, po);
      WHILE po.forwardTo # NIL DO
	 po := po.forwardTo;
      END;
      IF po.if = NIL THEN
	 dl := po.list;
	 WHILE (dl # NIL) & (dl.id # id) DO
	    dl := dl.next;
	 END;
	 IF dl # NIL THEN
	    discipline := dl.discipline;
	 ELSE
	    discipline := NIL;
	 END;
	 RETURN discipline # NIL
      ELSE
	 RETURN po.if.seek(po, id, discipline)
      END;
   END Seek;

   (* === interface procedures for PersistentObjects for Object === *)

   PROCEDURE ReadObjectData(stream: Streams.Stream;
                            object: PersistentObjects.Object) : BOOLEAN;
      (* read data and attached disciplines of given object from stream *)
      VAR
	 discipline: Discipline;
	 count: LONGINT;
   BEGIN
      (* get number of attached disciplines *)
      IF ~NetIO.ReadLongInt(stream, count) THEN
         RETURN FALSE;
      END;
      (* read all disciplines from `stream' and attach them to `object' *)
      WHILE count > 0 DO
	 IF ~PersistentObjects.Read(stream, SYS.VAL(PersistentObjects.Object, discipline)) THEN
	    RETURN FALSE;
	 END;
	 Add(object(Object), discipline);
	 DEC(count);
      END;
      RETURN TRUE;
   END ReadObjectData;

   PROCEDURE WriteObjectData(stream: Streams.Stream;
                             object: PersistentObjects.Object) : BOOLEAN;
      (* write data and attached disciplines of given object to stream *)
      VAR
	 dl: DisciplineList;
   BEGIN
      WITH object: Object DO
	 (* write number of attached disciplines to `stream' *)
	 IF ~NetIO.WriteLongInt(stream, object.count) THEN
	    RETURN FALSE;
	 END;
	 (* write all attached disciplines to the stream *)
	 dl := object.list;
	 WHILE dl # NIL DO
	    IF ~PersistentObjects.Write(stream, dl.discipline) THEN
	       RETURN FALSE;
	    END;
	    dl := dl.next;
	 END;
      END;
      RETURN TRUE;
   END WriteObjectData;

   PROCEDURE InternalCreate(VAR obj: PersistentObjects.Object);
      VAR
	 myObject: Object;
   BEGIN
      CreateObject(myObject);
      obj := myObject;
   END InternalCreate;

BEGIN
   unique := 0;

   NEW(objIf);
   objIf.read := ReadObjectData;
   objIf.write := WriteObjectData;
   objIf.create := InternalCreate;
   objIf.createAndRead := NIL;
   PersistentObjects.RegisterType(objDatatype, objectName, "", objIf);
   PersistentObjects.RegisterType(discDatatype, disciplineName, "", NIL);

   volDiscID := Disciplines.Unique();

   Forwarders.Register("", Forward);
END ulmPersistentDisciplines.
