MODULEIMPORT Env AS ProcEnv;ObValue EXPORTSObValue ,ObValueRep ; IMPORT Text, Fmt, SynWr, SynLocation, ObTree, AtomList, Atom, ObEval, NetObj, Pickle2 AS Pickle, PickleStubs, Rd, Wr, Thread, OSError, TextRefTbl, Refany, FileRd, FileWr, OpSys, SharedObj, NetObjNotifier; IMPORT ObValuePickle;
REVEAL
    ValRemObj = ValRemObjPublic BRANDED "ValRemObj" OBJECT OVERRIDES
      Who      := ValRemObjWho;
      Select   := ValRemObjSelect;
      Invoke   := ValRemObjInvoke;
      Update   := ValRemObjUpdate;
      Redirect := ValRemObjRedirect;
      Has      := ValRemObjHas;
      Obtain   := ValRemObjObtain;
    END;
    ValReplObj = ValReplObjPublic BRANDED "ValReplObj" OBJECT OVERRIDES
      Who      := ValReplObjWho;
      Select   := ValReplObjSelect;
      Invoke   := ValReplObjInvoke;
      Update   := ValReplObjUpdate;
      Redirect := ValReplObjRedirect;
      Has      := ValReplObjHas;
      Obtain   := ValReplObjObtain;
    END;
    ValSimpleObj = ValSimpleObjPublic BRANDED "ValSimpleObj" OBJECT
      OVERRIDES
        Who      := ValSimpleObjWho;
        Select   := ValSimpleObjSelect;
        Invoke   := ValSimpleObjInvoke;
        Update   := ValSimpleObjUpdate;
        Redirect := ValSimpleObjRedirect;
        Has      := ValSimpleObjHas;
        Obtain   := ValSimpleObjObtain;
      END;
  RemVarServer = RemVar BRANDED "RemVarServer" OBJECT
                   val: Val;
                 OVERRIDES
                   Get := VarGet;
                   Set := VarSet;
                 END;
  RemArrayServer = RemArray BRANDED "RemArrayServer" OBJECT
                     array: REF Vals;
                   OVERRIDES
                     Size   := ArraySize;
                     Get    := ArrayGet;
                     Set    := ArraySet;
                     Sub    := ArraySub;
                     Upd    := ArrayUpd;
                     Obtain := ArrayObtain;
                   END;
  RemObjServer = RemObjServerPublic BRANDED "RemObjServer" OBJECT
                   self     : ValRemObj;
                   fields   : REF ObjFields;
                   protected: BOOLEAN;
                 OVERRIDES
                   Who      := ObjWho;
                   Select   := ObjSelect;
                   Invoke   := ObjInvoke;
                   Update   := ObjUpdate;
                   Redirect := ObjRedirect;
                   Has      := ObjHas;
                   Obtain   := ObjObtain;
                 END;
  SimpleObj = SimpleObjPublic BRANDED "SimpleObj" OBJECT
                self     : ValSimpleObj;
                fields   : REF ObjFields;
                protected: BOOLEAN;
                pickleIn: REF ObjFields := NIL;
                pickleOut: REF ObjFields := NIL;
              OVERRIDES
                Who      := SimpleObjWho;
                Select   := SimpleObjSelect;
                Invoke   := SimpleObjInvoke;
                Update   := SimpleObjUpdate;
                Redirect := SimpleObjRedirect;
                Has      := SimpleObjHas;
                Obtain   := SimpleObjObtain;
              END;
  RemFileSystemServer = RemFileSystem BRANDED "RemFileSystemServer" OBJECT
                          readOnly: BOOLEAN;
                        OVERRIDES
                          OpenRead   := FileSystemOpenRead;
                          OpenWrite  := FileSystemOpenWrite;
                          OpenAppend := FileSystemOpenAppend;
                        END;
  NonRemObjHookServer = NonRemObjHook BRANDED "NonRemObjHookServer" OBJECT
    replObj: ValObj;
  OVERRIDES
    init := NonRemObjHookInit;
    Get := NonRemObjHookGet;
  END;
VAR sysCallTable: TextRefTbl.Default;
PROCEDURE ThisMachine  (): TEXT =
  BEGIN
    TRY
      RETURN OpSys.GetHostName();
    EXCEPT
    | OpSys.Error => RETURN "<unknown>";
    END;
  END ThisMachine;
PROCEDURE Setup  () =
  BEGIN
    valOk := NEW(ValOk);
    netException := NEW(ValException, name := "net_failure");
    sharedException := NEW(ValException, name := "shared_failure");
    sharedFatal := NEW(ValException, name := "shared_fatal");
    threadAlerted := NEW(ValException, name := "thread_alerted");
    machineAddress := ThisMachine();
    sysCallTable := NEW(TextRefTbl.Default).init();
    sysCallFailure := NEW(ValException, name := "sys_callFailure");
    showNetObjMsgs := FALSE;
    localProcessor := NewProcessor();
    InhibitTransmission(TYPECODE(ValProcessor),
                        "processors cannot be transmitted/duplicated");
  END Setup;
PROCEDURE RaiseError  (msg: TEXT; location: SynLocation.T) RAISES {Error} =
  BEGIN
    RAISE Error(NEW(ErrorPacket, msg := msg, location := location));
  END RaiseError;
PROCEDURE RaiseServerError  (msg: TEXT) RAISES {ServerError} =
  BEGIN
    RAISE ServerError(msg);
  END RaiseServerError;
PROCEDURE SameException  (exc1, exc2: ValException): BOOLEAN =
  BEGIN
    RETURN Text.Equal(exc1.name, exc2.name);
  END SameException;
PROCEDURE RaiseException  (exception: ValException;
                          msg      : TEXT;
                          loc      : SynLocation.T ) RAISES {Exception} =
  BEGIN
    RAISE Exception(NEW(ExceptionPacket, msg := msg, location := loc,
                        exception := exception, data := NIL));
  END RaiseException;
PROCEDURE RaiseNetException  (msg  : TEXT;
                             atoms: AtomList.T;
                             loc  : SynLocation.T) RAISES {Exception} =
  BEGIN
    IF showNetObjMsgs THEN
      msg := msg & " (NetObj says:";
      WHILE atoms # NIL DO
        msg := msg & " " & Atom.ToText(atoms.head);
        atoms := atoms.tail;
      END;
      msg := msg & ")";
    END;
    RaiseException(netException, msg, loc);
  END RaiseNetException;
PROCEDURE RaiseSharedException  (msg  : TEXT;
                                atoms: AtomList.T;
                                loc  : SynLocation.T) RAISES {Exception} =
  BEGIN
    IF showNetObjMsgs THEN
      msg := msg & " (SharedObj says:";
      WHILE atoms # NIL DO
        msg := msg & " " & Atom.ToText(atoms.head);
        atoms := atoms.tail;
      END;
      msg := msg & ")";
    END;
    RaiseException(sharedException, msg, loc);
  END RaiseSharedException;
PROCEDURE ErrorMsg  (swr: SynWr.T; packet: ErrorPacket) =
  BEGIN
    Msg(swr, "Execution error ", packet.msg, packet.location);
  END ErrorMsg;
PROCEDURE ExceptionMsg  (swr: SynWr.T; packet: ExceptionPacket) =
  VAR name: TEXT;
  BEGIN
    name := packet.exception.name;
    IF NOT Text.Empty(packet.msg) THEN
      name := name & " (" & packet.msg & ")";
    END;
    Msg(swr, "Uncaught exception ", name, packet.location);
  END ExceptionMsg;
PROCEDURE Msg  (swr           : SynWr.T;
               msgKind, msg  : TEXT;
               sourceLocation: SynLocation.T) =
  BEGIN
    SynWr.Beg(swr, 2, loud := TRUE);
    SynWr.Text(swr, msgKind, loud := TRUE);
    SynLocation.PrintLocation(swr, sourceLocation);
    SynWr.End(swr, loud := TRUE);
    SynWr.NewLine(swr, loud := TRUE);
    SynWr.Text(swr, msg, loud := TRUE);
    SynWr.NewLine(swr, loud := TRUE);
    SynWr.Flush(swr, loud := TRUE);
  END Msg;
PROCEDURE BadOp  (pkg, op: TEXT; location: SynLocation.T) RAISES {Error} =
  BEGIN
    RaiseError("Unknown operation: " & pkg & "_" & op, location);
  END BadOp;
PROCEDURE BadArgType  (argNo            : INTEGER;
                      expected, pkg, op: TEXT;
                      location         : SynLocation.T) RAISES {Error} =
  BEGIN
    RaiseError("Argument " & Fmt.Int(argNo) & " of " & pkg & "_" & op
                 & " must have type " & expected, location);
  END BadArgType;
PROCEDURE BadArgVal  (argNo            : INTEGER;
                     expected, pkg, op: TEXT;
                     location         : SynLocation.T) RAISES {Error} =
  BEGIN
    RaiseError("Argument " & Fmt.Int(argNo) & " of " & pkg & "_" & op
                 & " must be " & expected, location);
  END BadArgVal;
PROCEDURE NewEnv  (name: ObTree.IdeName; env: Env): Env =
  BEGIN
    RETURN NEW(LocalEnv, name := name, val := NIL, rest := env);
  END NewEnv;
PROCEDURE ExtendEnv  (binders: ObTree.IdeList; env: Env): Env =
  BEGIN
    IF binders = NIL THEN
      RETURN env;
    ELSE
      RETURN ExtendEnv(binders.rest, NewEnv(binders.first, env));
    END;
  END ExtendEnv;
PROCEDURE PrintWhat  (self: ValAnything): TEXT =
  BEGIN
    RETURN self.what;
  END PrintWhat;
PROCEDURE IsSelfOther  (self, other: ValAnything): BOOLEAN =
  BEGIN
    RETURN self = other;
  END IsSelfOther;
PROCEDURE Is  (v1, v2: Val; <*UNUSED*> location: SynLocation.T): BOOLEAN =
  BEGIN
    (* handle NILs explicitely *)
    IF v1 = NIL OR v2 = NIL THEN RETURN v1 = v2 END;
    TYPECASE v1 OF
    | ValOk =>
        TYPECASE v2 OF | ValOk => RETURN TRUE; ELSE RETURN FALSE; END;
    | ValBool (node1) =>
        TYPECASE v2 OF
        | ValBool (node2) => RETURN node1.bool = node2.bool;
        ELSE
          RETURN FALSE;
        END;
    | ValChar (node1) =>
        TYPECASE v2 OF
        | ValChar (node2) => RETURN node1.char = node2.char;
        ELSE
          RETURN FALSE;
        END;
    | ValText (node1) =>
        TYPECASE v2 OF
        | ValText (node2) => RETURN Text.Equal(node1.text, node2.text);
        ELSE
          RETURN FALSE;
        END;
    | ValException (node1) =>
        TYPECASE v2 OF
        | ValException (node2) =>
            RETURN Text.Equal(node1.name, node2.name);
        ELSE
          RETURN FALSE;
        END;
    | ValInt (node1) =>
        TYPECASE v2 OF
        | ValInt (node2) => RETURN node1.int = node2.int;
        ELSE
          RETURN FALSE;
        END;
    | ValReal (node1) =>
        TYPECASE v2 OF
        | ValReal (node2) => RETURN node1.real = node2.real;
        ELSE
          RETURN FALSE;
        END;
    | ValArray (node1) =>
        TYPECASE v2 OF
        | ValArray (node2) => RETURN node1.remote = node2.remote;
        ELSE
          RETURN FALSE;
        END;
    | ValAnything (node1) =>
        TYPECASE v2 OF
        | ValAnything (node2) => RETURN node1.Is(node2);
        ELSE
          RETURN FALSE;
        END;
    | ValOption (node1) =>
        TYPECASE v2 OF
        | ValOption (node2) => RETURN node1 = node2;
        ELSE
          RETURN FALSE;
        END;
    | ValFun (node1) =>
        TYPECASE v2 OF
        | ValFun (node2) => RETURN node1 = node2;
        ELSE
          RETURN FALSE;
        END;
    | ValMeth (node1) =>
        TYPECASE v2 OF
        | ValMeth (node2) => RETURN node1 = node2;
        ELSE
          RETURN FALSE;
        END;
      (* Obliq++: added handling for the 3 subtypes, and removed the
         generic ValObj supertype *)
    | ValRemObj (node1) =>
        TYPECASE v2 OF
        | ValRemObj (node2) => RETURN node1.remote = node2.remote;
        ELSE
          RETURN FALSE;
        END;
    | ValReplObj (node1) =>
        TYPECASE v2 OF
        | ValReplObj (node2) => RETURN node1.replica = node2.replica;
        ELSE
          RETURN FALSE;
        END;
    | ValSimpleObj (node1) =>
        TYPECASE v2 OF
        | ValSimpleObj (node2) => RETURN node1.simple = node2.simple;
        ELSE
          RETURN FALSE;
        END;
    | ValAlias (node1) =>
        TYPECASE v2 OF
        | ValAlias (node2) => RETURN node1 = node2;
        ELSE
          RETURN FALSE;
        END;
    | ValEngine (node1) =>
        TYPECASE v2 OF
        | ValEngine (node2) => RETURN node1.remote = node2.remote;
        ELSE
          RETURN FALSE;
        END;
    ELSE                         <*ASSERT FALSE*>
    END;
  END Is;
PROCEDURE NewText  (text: TEXT): Val =
  BEGIN
    IF text = NIL THEN text := "" END;
    RETURN NEW(ValText, text := text);
  END NewText;
PROCEDURE NewVar  (val: Val): ValVar =
  BEGIN
    RETURN NEW(ValVar, remote := NEW(RemVarServer, val := val));
  END NewVar;
PROCEDURE VarGet  (self: RemVarServer): Val RAISES {} =
  BEGIN
    RETURN self.val;
  END VarGet;
PROCEDURE VarSet  (self: RemVarServer; val: Val) RAISES {} =
  BEGIN
    self.val := val;
  END VarSet;
PROCEDURE NewArray  (READONLY vals: Vals): ValArray =
  VAR newVals: REF Vals;
  BEGIN
    newVals := NEW(REF Vals, NUMBER(vals));
    newVals^ := vals;
    RETURN NewArrayFromVals(newVals);
  END NewArray;
PROCEDURE NewArrayFromVals  (vals: REF Vals): ValArray =
  BEGIN
    RETURN NEW(ValArray, remote := NEW(RemArrayServer, array := vals));
  END NewArrayFromVals;
PROCEDURE ArraySize  (arr: RemArrayServer): INTEGER RAISES {} =
  BEGIN
    RETURN NUMBER(arr.array^);
  END ArraySize;
PROCEDURE ArrayGet  (self: RemArrayServer; i: INTEGER): Val
  RAISES {ServerError} =
  BEGIN
    IF (i < 0) OR (i >= NUMBER(self.array^)) THEN
      RaiseServerError("arg not in range")
    END;
    RETURN self.array^[i];
  END ArrayGet;
PROCEDURE ArraySet  (self: RemArrayServer; i: INTEGER; val: Val)
  RAISES {ServerError} =
  BEGIN
    IF (i < 0) OR (i >= NUMBER(self.array^)) THEN
      RaiseServerError("arg 1 not in range");
    END;
    self.array^[i] := val;
  END ArraySet;
PROCEDURE ArraySub  (self: RemArrayServer; start, size: INTEGER): ValArray
  RAISES {ServerError} =
  VAR
    len : INTEGER;
    vals: REF Vals;
  BEGIN
    len := NUMBER(self.array^);
    IF (start < 0) OR (start > len) THEN
      RaiseServerError("arg 2 not in range");
    END;
    IF (size < 0) OR (start + size > len) THEN
      RaiseServerError("arg 3 not in range");
    END;
    vals := NEW(REF Vals, size);
    FOR i := 0 TO size - 1 DO vals^[i] := self.array^[start + i]; END;
    RETURN NEW(ValArray, remote := NEW(RemArrayServer, array := vals));
  END ArraySub;
PROCEDURE ArrayUpd  (         self       : RemArrayServer;
                             start, size: INTEGER;
                    READONLY otherArr   : REF Vals        )
  RAISES {ServerError} =
  VAR
    selfLen, otherLen: INTEGER;
    selfArr          : REF Vals;
  BEGIN
    selfArr := self.array;
    selfLen := NUMBER(selfArr^);
    IF (start < 0) OR (start > selfLen) THEN
      RaiseServerError("arg 2 not in range");
    END;
    IF (size < 0) OR (start + size > selfLen) THEN
      RaiseServerError("arg 3 not in range of arg 1");
    END;
    otherLen := NUMBER(otherArr^);
    IF size > otherLen THEN
      RaiseServerError("arg 3 not in range of arg 4");
    END;
    FOR i := size - 1 TO 0 BY -1 DO
      selfArr^[start + i] := otherArr^[i];
    END;
  END ArrayUpd;
PROCEDURE ArrayObtain  (self: RemArrayServer): REF Vals RAISES {} =
  BEGIN
    RETURN self.array;
  END ArrayObtain;
PROCEDURE ArrayCat  (vals1, vals2: REF Vals): Val RAISES {} =
  VAR
    len1, len2: INTEGER;
    vals      : REF Vals;
  BEGIN
    len1 := NUMBER(vals1^);
    len2 := NUMBER(vals2^);
    vals := NEW(REF Vals, len1 + len2);
    FOR i := 0 TO len1 - 1 DO vals^[i] := vals1^[i]; END;
    FOR i := 0 TO len2 - 1 DO vals^[len1 + i] := vals2^[i]; END;
    RETURN NEW(ValArray, remote := NEW(RemArrayServer, array := vals));
  END ArrayCat;
PROCEDURE NewObject  (READONLY fields   : ObjFields;
                              who      : TEXT        := "";
                              protected: BOOLEAN     := FALSE;
                              sync     : Sync        := NIL    ): ValObj =
  VAR remFields: REF ObjFields;
  BEGIN
    remFields := NEW(REF ObjFields, NUMBER(fields));
    remFields^ := fields;
    RETURN NewObjectFromFields(remFields, who, protected, sync);
  END NewObject;
PROCEDURE NewObjectFromFields  (fields   : REF ObjFields;
                               who      : TEXT;
                               protected: BOOLEAN;
                               sync     : Sync           ): ValObj =
  VAR remObjServ: RemObjServer;
  BEGIN
    (* Obliq++: made the new object a ValRemObj *)
    remObjServ :=
      NEW(RemObjServer, who := who, self := NEW(ValRemObj, remote := NIL),
          fields := fields, protected := protected, sync := sync);
    remObjServ.self.remote := remObjServ;
    RETURN remObjServ.self;
  END NewObjectFromFields;
PROCEDURE NewReplObject  (READONLY fields   : ObjFields;
                         who      : TEXT        := "";
                         protected: BOOLEAN     := FALSE): ValObj
    RAISES {SharedObj.Error} =
  VAR replFields: REF ObjFields;
  BEGIN
    replFields := NEW(REF ObjFields, NUMBER(fields));
    replFields^ := fields;
    RETURN NewReplObjectFromFields(replFields, who, protected);
  END NewReplObject;
PROCEDURE NewReplObjectFromFields  (fields   : REF ObjFields;
                                   who      : TEXT;
                                   protected: BOOLEAN): ValObj
    RAISES {SharedObj.Error} =
  VAR replObjServ: ReplObjStd;
  BEGIN
    replObjServ := NEW(ReplObjStd, who := who,
                      self := NEW(ValReplObj, replica := NIL),
                      protected := protected,
                      fields := fields).init();
    replObjServ.self.replica := replObjServ;
    RETURN replObjServ.self;
  END NewReplObjectFromFields;
PROCEDURE NewSimpleObject  (READONLY fields   : ObjFields;
                           who      : TEXT        := "";
                           protected: BOOLEAN     := FALSE;
                           sync     : Sync        := NIL    ): ValObj =
  VAR simpleFields: REF ObjFields;
  BEGIN
    simpleFields := NEW(REF ObjFields, NUMBER(fields));
    simpleFields^ := fields;
    RETURN NewSimpleObjectFromFields(simpleFields, who, protected, sync);
  END NewSimpleObject;
PROCEDURE NewSimpleObjectFromFields  (fields   : REF ObjFields;
                                     who      : TEXT;
                                     protected: BOOLEAN;
                                     sync     : Sync           ): ValObj =
  VAR simpleObj: SimpleObj;
  BEGIN
    simpleObj := NEW(SimpleObj, who := who,
                     self := NEW(ValSimpleObj, simple := NIL),
                     fields := fields,
                     protected := protected, sync := sync);
    simpleObj.self.simple := simpleObj;
    RETURN simpleObj.self;
  END NewSimpleObjectFromFields;
**************************
 object conversion routines
 **************************
PROCEDURE************************** ValObj object wrapper functions **************************CloneObjData (valObj: ValObj; mySelf: ValObj; VAR resWho: TEXT; VAR resFields: REF ObjFields; VAR protected: BOOLEAN; VAR sync: Sync) RAISES {ServerError, NetObj.Error, SharedObj.Error, Thread.Alerted} = VAR who: TEXT; fieldsOf1: REF ObjFields; resSize : INTEGER; serialized: BOOLEAN; BEGIN who := valObj.Who( (*out*)protected, (*out*) serialized); IF Text.Empty(who) THEN who := "someone" END; resWho := "clone of " & who; fieldsOf1 := valObj.Obtain(ObjEqual(valObj, mySelf)); resSize := NUMBER(fieldsOf1^); resFields := NEW(REF ObjFields, resSize); resFields^ := fieldsOf1^; IF serialized THEN sync := NEW(Sync, mutex := NEW(Thread.Mutex)) ELSE sync := NIL END; END CloneObjData; PROCEDUREToSimpleObj (READONLY obj: ValObj; mySelf: ValObj): ValObj RAISES {ServerError, NetObj.Error, SharedObj.Error, Thread.Alerted} = VAR resWho: TEXT; resFields: REF ObjFields; protected: BOOLEAN; sync : Sync; BEGIN CloneObjData(obj, mySelf, resWho, resFields, protected, sync); WITH res = NEW(SimpleObj, who := resWho, self := NEW(ValSimpleObj, simple := NIL), fields := resFields, protected := protected, sync := sync) DO res.self.simple := res; RETURN res.self; END; END ToSimpleObj; PROCEDUREToReplObj (READONLY obj: ValObj; mySelf: ValObj; READONLY updateMethods: ARRAY OF TEXT): ValObj RAISES {ServerError, NetObj.Error, SharedObj.Error, Thread.Alerted} = VAR resWho: TEXT; resFields: REF ObjFields; protected: BOOLEAN; sync : Sync; j : INTEGER; BEGIN CloneObjData(obj, mySelf, resWho, resFields, protected, sync); (* why bother failing? What's the big deal!? It becomes serialized, that's all. IF sync = NIL THEN RaiseServerError("Replicated Objects must be serialized"); END; *) FOR i := FIRST(resFields^) TO LAST(resFields^) DO resFields^[i].update := FALSE; END; FOR i := FIRST(updateMethods) TO LAST(updateMethods) DO j := FIRST(resFields^); WHILE j <= LAST(resFields^) DO IF Text.Equal(updateMethods[i], resFields^[j].label) THEN TYPECASE resFields^[j].field OF | ValMeth => resFields^[j].update := TRUE; EXIT; ELSE RaiseServerError("field '" & updateMethods[i] & "' is not a method"); END; END; INC(j); END; IF j > LAST(resFields^) THEN RaiseServerError("update method '" & updateMethods[i] & "' does not exist"); END; END; WITH res = NEW(ReplObjStd, who := resWho, protected := protected, self := NEW(ValReplObj, replica := NIL), fields := resFields).init() DO res.self.replica := res; RETURN res.self; END; END ToReplObj; PROCEDUREToRemObj (READONLY obj: ValObj; mySelf: ValObj): ValObj RAISES {ServerError, NetObj.Error, SharedObj.Error, Thread.Alerted} = VAR resWho: TEXT; resFields: REF ObjFields; protected: BOOLEAN; sync : Sync; BEGIN CloneObjData(obj, mySelf, resWho, resFields, protected, sync); WITH res = NEW(RemObjServer, who := resWho, self := NEW(ValRemObj, remote := NIL), fields := resFields, protected := protected, sync := sync) DO res.self.remote := res; RETURN res.self; END; END ToRemObj;
PROCEDURE************************** object fields **************************ValRemObjWho (self: ValRemObj; VAR(*out*) protected, serialized: BOOLEAN): TEXT RAISES {NetObj.Error, Thread.Alerted} = BEGIN RETURN self.remote.Who(protected, serialized); END ValRemObjWho; PROCEDUREValRemObjSelect (self: ValRemObj; label: TEXT; internal: BOOLEAN; VAR hint: INTEGER): Val RAISES {Error, Exception, ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN RETURN self.remote.Select(label, internal, hint); END ValRemObjSelect; PROCEDUREValRemObjInvoke (self: ValRemObj; label: TEXT; argNo: INTEGER; READONLY args: Vals; internal: BOOLEAN; VAR hint: INTEGER): Val RAISES {Error, Exception, ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN RETURN self.remote.Invoke(label, argNo, args, internal, hint); END ValRemObjInvoke; PROCEDUREValRemObjUpdate (self: ValRemObj; label: TEXT; val: Val; internal: BOOLEAN; VAR hint: INTEGER) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN self.remote.Update(label, val, internal, hint); END ValRemObjUpdate; PROCEDUREValRemObjRedirect (self: ValRemObj; val: Val; internal: BOOLEAN) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN self.remote.Redirect(val, internal); END ValRemObjRedirect; PROCEDUREValRemObjHas (self: ValRemObj; label: TEXT; VAR hint: INTEGER): BOOLEAN RAISES {NetObj.Error, Thread.Alerted} = BEGIN RETURN self.remote.Has(label, hint); END ValRemObjHas; PROCEDUREValRemObjObtain (self: ValRemObj; internal: BOOLEAN): REF ObjFields RAISES {ServerError, NetObj.Error, Thread.Alerted} = BEGIN RETURN self.remote.Obtain(internal); END ValRemObjObtain; PROCEDUREValReplObjWho (self: ValReplObj; VAR(*out*) protected, serialized: BOOLEAN): TEXT RAISES {SharedObj.Error} = BEGIN serialized := TRUE; RETURN self.replica.Who(protected); END ValReplObjWho; PROCEDUREValReplObjSelect (self: ValReplObj; label: TEXT; <*UNUSED*>internal: BOOLEAN; VAR hint: INTEGER): Val RAISES {Error, Exception, ServerError, SharedObj.Error} = BEGIN RETURN self.replica.Select(label, hint); END ValReplObjSelect; PROCEDUREValReplObjInvoke (self: ValReplObj; label: TEXT; argNo: INTEGER; READONLY args: Vals; <*UNUSED*>internal: BOOLEAN; VAR hint: INTEGER): Val RAISES {Error, Exception, ServerError, SharedObj.Error} = BEGIN RETURN self.replica.Invoke(label, argNo, args, hint); END ValReplObjInvoke; PROCEDUREValReplObjUpdate (self: ValReplObj; label: TEXT; val: Val; internal: BOOLEAN; VAR hint: INTEGER) RAISES {ServerError, SharedObj.Error} = BEGIN self.replica.Update(label, val, internal, hint); END ValReplObjUpdate; PROCEDUREValReplObjRedirect (<*UNUSED*>self: ValReplObj; <*UNUSED*>val: Val; <*UNUSED*>internal: BOOLEAN) RAISES {ServerError} = BEGIN RaiseServerError("Cannot Redirect Replicated Object Fields"); END ValReplObjRedirect; PROCEDUREValReplObjHas (self: ValReplObj; label: TEXT; VAR hint: INTEGER): BOOLEAN RAISES {SharedObj.Error} = BEGIN RETURN self.replica.Has(label, hint); END ValReplObjHas; PROCEDUREValReplObjObtain (self: ValReplObj; internal: BOOLEAN): REF ObjFields RAISES {ServerError, SharedObj.Error} = BEGIN RETURN self.replica.Obtain(internal); END ValReplObjObtain; PROCEDUREValSimpleObjWho (self: ValSimpleObj; VAR(*out*) protected, serialized: BOOLEAN): TEXT = BEGIN RETURN self.simple.Who(protected, serialized); END ValSimpleObjWho; PROCEDUREValSimpleObjSelect (self: ValSimpleObj; label: TEXT; internal: BOOLEAN; VAR hint: INTEGER): Val RAISES {Error, Exception, ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN RETURN self.simple.Select(label, internal, hint); END ValSimpleObjSelect; PROCEDUREValSimpleObjInvoke (self: ValSimpleObj; label: TEXT; argNo: INTEGER; READONLY args: Vals; internal: BOOLEAN; VAR hint: INTEGER): Val RAISES {Error, Exception, ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN RETURN self.simple.Invoke(label, argNo, args, internal, hint); END ValSimpleObjInvoke; PROCEDUREValSimpleObjUpdate (self: ValSimpleObj; label: TEXT; val: Val; internal: BOOLEAN; VAR hint: INTEGER) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN self.simple.Update(label, val, internal, hint); END ValSimpleObjUpdate; PROCEDUREValSimpleObjRedirect (self: ValSimpleObj; val: Val; internal: BOOLEAN) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = BEGIN self.simple.Redirect(val, internal); END ValSimpleObjRedirect; PROCEDUREValSimpleObjHas (self: ValSimpleObj; label: TEXT; VAR hint: INTEGER): BOOLEAN = BEGIN RETURN self.simple.Has(label, hint); END ValSimpleObjHas; PROCEDUREValSimpleObjObtain (self: ValSimpleObj; internal: BOOLEAN): REF ObjFields RAISES {ServerError} = BEGIN RETURN self.simple.Obtain(internal); END ValSimpleObjObtain;
PROCEDURE=== notification for remote object disappearance ===ObjWho ( self : RemObjServer; VAR (*out*) protected, serialized: BOOLEAN ): TEXT = BEGIN protected := self.protected; serialized := self.sync # NIL; RETURN self.who; END ObjWho; PROCEDUREReplObjWho (self: ReplObj; VAR (*out*) protected: BOOLEAN ): TEXT = BEGIN protected := self.protected; RETURN self.who; END ReplObjWho; PROCEDURESimpleObjWho ( self : SimpleObj; VAR (*out*) protected, serialized: BOOLEAN ): TEXT = BEGIN protected := self.protected; serialized := self.sync # NIL; RETURN self.who; END SimpleObjWho; PROCEDUREObjEqual (v1, v2: ValObj): BOOLEAN = BEGIN IF v1 = NIL OR v2 = NIL THEN RETURN v1 = v2 END; TYPECASE v1 OF | ValRemObj (node1) => TYPECASE v2 OF | ValRemObj (node2) => RETURN node1.remote = node2.remote; ELSE RETURN FALSE; END; | ValReplObj (node1) => TYPECASE v2 OF | ValReplObj (node2) => RETURN node1.replica = node2.replica; ELSE RETURN FALSE; END; | ValSimpleObj (node1) => TYPECASE v2 OF | ValSimpleObj (node2) => RETURN node1.simple = node2.simple; ELSE RETURN FALSE; END; ELSE RETURN FALSE; END; END ObjEqual; PROCEDUREObjClone1 (valObj: ValObj; mySelf: ValObj): ValObj RAISES {ServerError, NetObj.Error, SharedObj.Error, Thread.Alerted} = VAR resWho, who: TEXT; VAR fieldsOf1: REF ObjFields; VAR resSize : INTEGER; resFields: REF ObjFields; VAR protected, serialized: BOOLEAN; sync : Sync; BEGIN who := valObj.Who( (*out*)protected, (*out*) serialized); IF Text.Empty(who) THEN who := "someone" END; resWho := "clone of " & who; fieldsOf1 := valObj.Obtain(ObjEqual(valObj, mySelf)); resSize := NUMBER(fieldsOf1^); resFields := NEW(REF ObjFields, resSize); resFields^ := fieldsOf1^; IF serialized THEN sync := NEW(Sync, mutex := NEW(Thread.Mutex)) ELSE sync := NIL END; (* Obliq++: made the new object a ValRemObj *) TYPECASE valObj OF | ValRemObj => WITH res = NEW(RemObjServer, who := resWho, self := NEW(ValRemObj, remote := NIL), fields := resFields, protected := protected, sync := sync) DO res.self.remote := res; RETURN res.self; END; | ValReplObj => WITH res = NEW(ReplObjStd, who := resWho, protected := protected, self := NEW(ValReplObj, replica := NIL), fields := resFields).init() DO res.self.replica := res; RETURN res.self; END; | ValSimpleObj => WITH res = NEW(SimpleObj, who := resWho, self := NEW(ValSimpleObj, simple := NIL), fields := resFields, protected := protected, sync := sync) DO res.self.simple := res; RETURN res.self; END; ELSE <*ASSERT FALSE*> END; END ObjClone1; PROCEDUREObjClone (READONLY valObjs: ARRAY OF ValObj; mySelf: ValObj): ValObj RAISES {ServerError, NetObj.Error, Thread.Alerted, SharedObj.Error} = VAR resWho, remWho: TEXT; VAR fieldsOfN: REF ARRAY OF REF ObjFields; VAR resSize, k : INTEGER; ithFields, resFields: REF ObjFields; VAR protected, protected1, serialized, serialized1: BOOLEAN; sync : Sync; BEGIN (* First, check to make sure they are all the same type *) TYPECASE valObjs[0] OF | ValRemObj => FOR i := 1 TO NUMBER(valObjs) - 1 DO TYPECASE valObjs[i] OF ValRemObj => (* ok *) ELSE RaiseServerError("Objects to be cloned must be of the same type"); END; END; | ValReplObj => FOR i := 1 TO NUMBER(valObjs) - 1 DO TYPECASE valObjs[i] OF ValReplObj => (* ok *) ELSE RaiseServerError("Objects to be cloned must be of the same type"); END; END; | ValSimpleObj => FOR i := 1 TO NUMBER(valObjs) - 1 DO TYPECASE valObjs[i] OF ValSimpleObj => (* ok *) ELSE RaiseServerError("Objects to be cloned must be of the same type"); END; END; ELSE RaiseServerError("Arguments of clone must be objects"); END; resWho := "clone of"; protected := FALSE; serialized := FALSE; fieldsOfN := NEW(REF ARRAY OF REF ObjFields, NUMBER(valObjs)); FOR i := 0 TO NUMBER(valObjs) - 1 DO remWho := valObjs[i].Who( (*out*)protected1, (*out*) serialized1); IF i = 0 THEN protected := protected1; serialized := serialized1; END; IF Text.Empty(remWho) THEN remWho := "someone" END; resWho := resWho & " " & remWho; fieldsOfN^[i] := valObjs[i].Obtain(ObjEqual(valObjs[i], mySelf)); END; resSize := 0; FOR i := 0 TO NUMBER(fieldsOfN^) - 1 DO ithFields := fieldsOfN^[i]; INC(resSize, NUMBER(ithFields^)); END; resFields := NEW(REF ObjFields, resSize); k := 0; FOR i := 0 TO NUMBER(fieldsOfN^) - 1 DO ithFields := fieldsOfN^[i]; FOR j := 0 TO NUMBER(ithFields^) - 1 DO resFields^[k] := ithFields^[j]; INC(k); END; END; IF NUMBER(fieldsOfN^) > 1 THEN FOR i := 0 TO resSize - 1 DO FOR j := i + 1 TO resSize - 1 DO IF Text.Equal(resFields^[i].label, resFields^[j].label) THEN RaiseServerError( "duplicated field on cloning: " & resFields^[i].label); END; END; END; END; IF serialized THEN sync := NEW(Sync, mutex := NEW(Thread.Mutex)) ELSE sync := NIL END; TYPECASE valObjs[0] OF | ValRemObj => WITH res = NEW(RemObjServer, who := resWho, self := NEW(ValRemObj, remote := NIL), fields := resFields, protected := protected, sync := sync) DO res.self.remote := res; RETURN res.self; END; | ValReplObj => WITH res = NEW(ReplObjStd, who := resWho, protected := protected, self := NEW(ValReplObj, replica := NIL), fields := resFields).init() DO res.self.replica := res; RETURN res.self; END; | ValSimpleObj => WITH res = NEW(SimpleObj, who := resWho, self := NEW(ValSimpleObj, simple := NIL), fields := resFields, protected := protected, sync := sync) DO res.self.simple := res; RETURN res.self; END; ELSE <*ASSERT FALSE*> END; END ObjClone; PROCEDURESetObjPickler (obj: ValObj; picklerIn: ValSimpleObj; picklerOut: ValSimpleObj; mySelf: ValObj) RAISES {ServerError, NetObj.Error, SharedObj.Error, Thread.Alerted} = VAR objFields := obj.Obtain(ObjEqual(obj, mySelf)); pklInFields := picklerIn.Obtain(ObjEqual(obj, mySelf)); pklOutFields := picklerOut.Obtain(ObjEqual(obj, mySelf)); inFields := NEW(REF ObjFields, NUMBER(objFields^)); outFields := NEW(REF ObjFields, NUMBER(objFields^)); hint: INTEGER; numFields := 0; BEGIN TYPECASE obj OF | ValSimpleObj, ValReplObj => (*ok*) ELSE RaiseServerError("Can only set picklers for simple or " & "replicated objects"); END; IF NUMBER(pklInFields^) # NUMBER(pklOutFields^) THEN RaiseServerError("in and out pickler objects must have the same " & "set of fields"); END; IF NUMBER(objFields^) < NUMBER(pklInFields^) THEN RaiseServerError("pickler objects have extra fields"); END; (* want to have the inFields and outFields be in the same order as the objects fields, for later efficient use *) FOR i := 0 TO NUMBER(objFields^) - 1 DO TYPECASE objFields[i].field OF | ValMeth => (* ignore *) IF FieldsHave (pklInFields, objFields[i].label, hint) OR FieldsHave (pklOutFields, objFields[i].label, hint) THEN RaiseServerError("field in pickle object corresponds to " & "method field in object: " & objFields[i].label); END; (* put some dummy values for simplicity *) inFields[i].label := objFields[i].label; inFields[i].field := valOk; outFields[i].label := objFields[i].label; outFields[i].field := valOk; | ValAlias => RaiseServerError("Unexpected Alias field in " & "replicated object: " & objFields[i].label); ELSE IF NOT FieldsHave(pklInFields, objFields[i].label, hint) THEN RaiseServerError("pickler 'in' object missing field: " & objFields[i].label); END; inFields[i].label := objFields[i].label; TYPECASE pklInFields[hint].field OF | ValMeth(meth) => IF meth.meth.bindersNo # 3 THEN RaiseServerError(BadArgsNoMsg(3, meth.meth.bindersNo, "pickle 'in' method", objFields[i].label)); END; inFields[i].field := meth; ELSE RaiseServerError("pickler 'in' field must be a method: " & objFields[i].label); END; IF NOT FieldsHave(pklOutFields, objFields[i].label, hint) THEN RaiseServerError("pickler 'out' object missing field: " & objFields[i].label); END; outFields[i].label := objFields[i].label; TYPECASE pklOutFields[hint].field OF | ValMeth(meth) => IF meth.meth.bindersNo # 3 THEN RaiseServerError(BadArgsNoMsg(3, meth.meth.bindersNo, "pickler 'out' method", objFields[i].label)); END; outFields[i].field := meth; ELSE RaiseServerError("pickler 'out' field must be a method: " & objFields[i].label); END; INC(numFields); END; END; IF numFields # NUMBER(pklOutFields^) THEN RaiseServerError("extra fields in pickler objects"); END; TYPECASE obj OF | ValSimpleObj(simple) => simple.simple.pickleIn := inFields; simple.simple.pickleOut := outFields; | ValReplObj(repl) => repl.replica.pickleIn := inFields; repl.replica.pickleOut := outFields; ELSE <*ASSERT FALSE*> END; END SetObjPickler; PROCEDUREBadArgsNoMsg (desired, found : INTEGER; routineKind, routineName: TEXT ): TEXT = VAR msg: TEXT; BEGIN msg := "Expecting " & Fmt.Int(desired); IF desired = 1 THEN msg := msg & " argument"; ELSE msg := msg & " arguments"; END; msg := msg & ", not " & Fmt.Int(found); IF NOT Text.Empty(routineKind) THEN msg := msg & ", for " & routineKind & ": " & routineName; END; RETURN msg; END BadArgsNoMsg; PROCEDURENonRemObjHookGet (self: NonRemObjHookServer): ValObj = BEGIN RETURN self.replObj; END NonRemObjHookGet; PROCEDURENonRemObjHookInit (self: NonRemObjHookServer; replObj: ValObj): NonRemObjHook = BEGIN self.replObj := replObj; RETURN self; END NonRemObjHookInit; <*INLINE*> PROCEDUREFindField ( label : TEXT; fields: REF ObjFields; VAR hint : INTEGER ): Val RAISES {ServerError} = VAR fieldIndex := -1; BEGIN WITH fieldsNo = NUMBER(fields^) DO IF (hint >= 0) AND (hint < fieldsNo) AND Text.Equal(label, fields^[hint].label) THEN (* use hint as is *) ELSE FOR i := 0 TO fieldsNo - 1 DO IF Text.Equal(label, fields^[i].label) THEN fieldIndex := i; EXIT; END; END; IF fieldIndex = -1 THEN RaiseServerError("Field not found in object: " & label); END; hint := fieldIndex; END; END; RETURN fields^[hint].field; END FindField; PROCEDUREObjSelect ( self : RemObjServer; label : TEXT; internal: BOOLEAN; VAR (*in-out*) hint : INTEGER ): Val RAISES {ServerError, Error, Exception, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields : REF ObjFields; newEnv : Env; fieldVal: Val; objMu : Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY fields := self.fields; fieldVal := FindField(label, fields, hint); TYPECASE fieldVal OF | ValMeth (meth) => (* Consider a method with zero parameters as a field. *) IF meth.meth.bindersNo - 1 # 0 THEN RaiseServerError( BadArgsNoMsg(meth.meth.bindersNo - 1, 0, "method", label)); END; newEnv := NEW(LocalEnv, name := meth.meth.binders.first, val := self.self, rest := NIL); RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); | ValAlias (alias) => RETURN alias.obj.Select(alias.label, ObjEqual(alias.obj, self.self), (*var*) alias.labelIndexHint); ELSE RETURN fieldVal; END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END ObjSelect; PROCEDURESimpleObjSelect ( self : SimpleObj; label : TEXT; internal: BOOLEAN; VAR hint : INTEGER ): Val RAISES {ServerError, Error, Exception, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields : REF ObjFields; newEnv : Env; fieldVal: Val; objMu : Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY fields := self.fields; fieldVal := FindField(label, fields, hint); TYPECASE fieldVal OF | ValMeth (meth) => (* Consider a method with zero parameters as a field. *) IF meth.meth.bindersNo - 1 # 0 THEN RaiseServerError( BadArgsNoMsg(meth.meth.bindersNo - 1, 0, "method", label)); END; newEnv := NEW(LocalEnv, name := meth.meth.binders.first, val := self.self, rest := NIL); RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); | ValAlias (alias) => RETURN alias.obj.Select(alias.label, ObjEqual(alias.obj, self.self), (*var*) alias.labelIndexHint); ELSE RETURN fieldVal; END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END SimpleObjSelect; PROCEDUREReplObjSelect ( self : ReplObj; label : TEXT; VAR hint : INTEGER ): Val RAISES {Error, Exception, ServerError, SharedObj.Error} = VAR fields := self.fields; newEnv : Env; fieldVal: Val; BEGIN fieldVal := FindField(label, fields, hint); TYPECASE fieldVal OF | ValMeth (meth) => (* Consider a method with zero parameters as a field. *) IF meth.meth.bindersNo - 1 # 0 THEN RaiseServerError( BadArgsNoMsg(meth.meth.bindersNo - 1, 0, "method", label)); END; (* If it is not an update method, we can execute it here. If it is an update method, we must call InvokeUpdate *) IF fields^[hint].update THEN VAR args := ARRAY [0..0] OF Val{NIL}; BEGIN RETURN self.InvokeUpdate(label, 0, args, hint); END; ELSE newEnv := NEW(LocalEnv, name := meth.meth.binders.first, val := self.self, rest := NIL); RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); END; | ValAlias => <*ASSERT FALSE*>(* should not be any aliases on replicated object fields *) ELSE RETURN fieldVal; END; END ReplObjSelect; PROCEDUREFieldsHave (fields: REF ObjFields; label: TEXT; VAR hint: INTEGER): BOOLEAN = BEGIN FOR i := 0 TO NUMBER(fields^) - 1 DO IF Text.Equal(label, fields^[i].label) THEN hint := i; RETURN TRUE; END; END; RETURN FALSE; END FieldsHave; PROCEDUREObjHas (self: RemObjServer; label: TEXT; VAR hint: INTEGER): BOOLEAN = BEGIN RETURN FieldsHave(self.fields, label, hint); END ObjHas; PROCEDUREReplObjHas (self: ReplObj; label: TEXT; VAR hint: INTEGER): BOOLEAN = BEGIN RETURN FieldsHave(self.fields, label, hint); END ReplObjHas; PROCEDURESimpleObjHas (self: SimpleObj; label: TEXT; VAR hint: INTEGER): BOOLEAN = BEGIN RETURN FieldsHave(self.fields, label, hint); END SimpleObjHas; PROCEDUREObjInvoke ( self : RemObjServer; label : TEXT; argsNo : INTEGER; READONLY args : Vals; internal: BOOLEAN; VAR (*in-out*) hint : INTEGER ): Val RAISES {ServerError, Error, Exception, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields : REF ObjFields; binderList: ObTree.IdeList; newEnv : Env; fieldVal : Val; objMu : Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY fields := self.fields; fieldVal := FindField(label, fields, hint); TYPECASE fieldVal OF | ValMeth (meth) => IF meth.meth.bindersNo - 1 # argsNo THEN RaiseServerError(BadArgsNoMsg(meth.meth.bindersNo - 1, argsNo, "method", label)); END; binderList := meth.meth.binders; newEnv := NEW(LocalEnv, name := binderList.first, val := self.self, rest := NIL); binderList := binderList.rest; FOR i := 0 TO argsNo - 1 DO newEnv := NEW(LocalEnv, name := binderList.first, val := args[i], rest := newEnv); binderList := binderList.rest; END; RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); | ValAlias (alias) => RETURN alias.obj.Invoke(alias.label, argsNo, args, ObjEqual(alias.obj, self.self), (*var*) alias.labelIndexHint); ELSE RaiseServerError("Field used as a method: " & label); <*ASSERT FALSE*> END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END ObjInvoke; PROCEDURESimpleObjInvoke ( self : SimpleObj; label : TEXT; argsNo : INTEGER; READONLY args : Vals; internal: BOOLEAN; VAR hint : INTEGER ): Val RAISES {ServerError, Error, Exception, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields : REF ObjFields; binderList: ObTree.IdeList; newEnv : Env; fieldVal : Val; objMu : Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY fields := self.fields; fieldVal := FindField(label, fields, hint); TYPECASE fieldVal OF | ValMeth (meth) => IF meth.meth.bindersNo - 1 # argsNo THEN RaiseServerError(BadArgsNoMsg(meth.meth.bindersNo - 1, argsNo, "method", label)); END; binderList := meth.meth.binders; newEnv := NEW(LocalEnv, name := binderList.first, val := self.self, rest := NIL); binderList := binderList.rest; FOR i := 0 TO argsNo - 1 DO newEnv := NEW(LocalEnv, name := binderList.first, val := args[i], rest := newEnv); binderList := binderList.rest; END; RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); | ValAlias (alias) => RETURN alias.obj.Invoke(alias.label, argsNo, args, ObjEqual(alias.obj, self.self), (*var*) alias.labelIndexHint); ELSE RaiseServerError("Field used as a method: " & label); <*ASSERT FALSE*> END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END SimpleObjInvoke; PROCEDUREReplObjInvoke ( self : ReplObj; label : TEXT; argsNo : INTEGER; READONLY args : Vals; VAR hint : INTEGER ): Val RAISES {Error, Exception, ServerError, SharedObj.Error} = VAR fields : REF ObjFields; binderList: ObTree.IdeList; newEnv : Env; fieldVal : Val; BEGIN fields := self.fields; fieldVal := FindField(label, fields, hint); (* If it's an update method, do perform the update instead *) IF fields^[hint].update THEN RETURN self.InvokeUpdate(label, argsNo, args, hint); END; TYPECASE fieldVal OF | ValMeth (meth) => IF meth.meth.bindersNo - 1 # argsNo THEN RaiseServerError( BadArgsNoMsg(meth.meth.bindersNo - 1, argsNo, "method", label)); END; binderList := meth.meth.binders; newEnv := NEW(LocalEnv, name := binderList.first, val := self.self, rest := NIL); binderList := binderList.rest; FOR i := 0 TO argsNo - 1 DO newEnv := NEW(LocalEnv, name := binderList.first, val := args[i], rest := newEnv); binderList := binderList.rest; END; RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); | ValAlias => <*ASSERT FALSE*>(* should never happen *) ELSE RaiseServerError("Field used as a method: " & label); <*ASSERT FALSE*> END; END ReplObjInvoke; PROCEDUREReplObjInvokeUpdate ( self : ReplObj; label : TEXT; argsNo : INTEGER; READONLY args : Vals; VAR hint : INTEGER ): Val RAISES {Error, Exception, ServerError} = VAR fields : REF ObjFields; binderList: ObTree.IdeList; newEnv : Env; fieldVal : Val; BEGIN fields := self.fields; fieldVal := FindField(label, fields, hint); TYPECASE fieldVal OF | ValMeth (meth) => IF meth.meth.bindersNo - 1 # argsNo THEN RaiseServerError( BadArgsNoMsg(meth.meth.bindersNo - 1, argsNo, "method", label)); END; binderList := meth.meth.binders; newEnv := NEW(LocalEnv, name := binderList.first, val := self.self, rest := NIL); binderList := binderList.rest; FOR i := 0 TO argsNo - 1 DO newEnv := NEW(LocalEnv, name := binderList.first, val := args[i], rest := newEnv); binderList := binderList.rest; END; RETURN ObEval.Term(meth.meth.body, (*in-out*) newEnv, meth.global, self.self); | ValAlias => <*ASSERT FALSE*>(* should never happen *) ELSE RaiseServerError("Field used as a method: " & label); <*ASSERT FALSE*> END; END ReplObjInvokeUpdate; PROCEDUREObjUpdate ( self : RemObjServer; label : TEXT; val : Val; internal: BOOLEAN; VAR (*in-out*) hint : INTEGER ) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields : REF ObjFields; objMu : Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot update protected object"); END; fields := self.fields; EVAL FindField(label, fields, hint); TYPECASE fields^[hint].field OF | ValAlias (alias) => TYPECASE val OF | ValAlias => fields^[hint].field := val ELSE alias.obj.Update(alias.label, val, ObjEqual(alias.obj, self.self), (*var*) alias.labelIndexHint); END; ELSE fields^[hint].field := val; END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END ObjUpdate; PROCEDURESimpleObjUpdate ( self : SimpleObj; label : TEXT; val : Val; internal: BOOLEAN; VAR hint : INTEGER ) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields : REF ObjFields; objMu : Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot update protected object"); END; fields := self.fields; EVAL FindField(label, fields, hint); TYPECASE fields^[hint].field OF | ValAlias (alias) => TYPECASE val OF | ValAlias => fields^[hint].field := val ELSE alias.obj.Update(alias.label, val, ObjEqual(alias.obj, self.self), (*var*) alias.labelIndexHint); END; ELSE fields^[hint].field := val; END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END SimpleObjUpdate; PROCEDUREReplObjUpdate ( self : ReplObj; label : TEXT; val : Val; internal: BOOLEAN; VAR hint : INTEGER ) RAISES {ServerError} = VAR fields : REF ObjFields; BEGIN IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot update protected object"); END; TYPECASE val OF | ValAlias => RaiseServerError("Cannot alias fields in a replicated object"); ELSE END; fields := self.fields; EVAL FindField(label, fields, hint); TYPECASE fields^[hint].field OF | ValAlias => <* ASSERT FALSE *> (* should be impossible *) ELSE fields^[hint].field := val; END; END ReplObjUpdate; PROCEDUREObjRedirect (self: RemObjServer; val: Val; internal: BOOLEAN) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields, newFields: REF ObjFields; fieldsNo : INTEGER; label : TEXT; hint : INTEGER; objMu : Thread.Mutex; valObj : ValObj; BEGIN TYPECASE val OF ValObj(vo) => valObj := vo ELSE RaiseServerError("Redirection target must be an object"); END; lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot redirect protected object"); END; fields := self.fields; fieldsNo := NUMBER(fields^); newFields := NEW(REF ObjFields, fieldsNo); FOR i := 0 TO fieldsNo - 1 DO label := fields^[i].label; newFields^[i].label := label; IF valObj.Has(label, (*in-out*) hint) THEN newFields^[i].field := NEW(ValAlias, label := label, labelIndexHint := hint, obj := valObj); ELSE RaiseServerError("Field not found in object on redirection: " & label); END; self.fields := newFields; (* atomic swap *) END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END ObjRedirect; PROCEDURESimpleObjRedirect (self: SimpleObj; val: Val; internal: BOOLEAN) RAISES {ServerError, SharedObj.Error, NetObj.Error, Thread.Alerted} = VAR lock : BOOLEAN; fields, newFields: REF ObjFields; fieldsNo : INTEGER; label : TEXT; hint : INTEGER; objMu : Thread.Mutex; valObj : ValObj; BEGIN TYPECASE val OF ValObj(vo) => valObj := vo ELSE RaiseServerError("Redirection target must be an object"); END; lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot redirect protected object"); END; fields := self.fields; fieldsNo := NUMBER(fields^); newFields := NEW(REF ObjFields, fieldsNo); FOR i := 0 TO fieldsNo - 1 DO label := fields^[i].label; newFields^[i].label := label; IF valObj.Has(label, (*in-out*) hint) THEN newFields^[i].field := NEW(ValAlias, label := label, labelIndexHint := hint, obj := valObj); ELSE RaiseServerError( "Field not found in object on redirection: " & label); END; self.fields := newFields; (* atomic swap *) END; FINALLY IF lock THEN Thread.Release(objMu) END; END; END SimpleObjRedirect; PROCEDUREObjObtain (self: RemObjServer; internal: BOOLEAN): REF ObjFields RAISES {ServerError} = VAR lock : BOOLEAN; objMu: Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot obtain protected object"); END; RETURN self.fields; FINALLY IF lock THEN Thread.Release(objMu) END; END; END ObjObtain; PROCEDUREReplObjObtain (self: ReplObj; internal: BOOLEAN): REF ObjFields RAISES {ServerError} = BEGIN IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot obtain protected object"); END; RETURN self.fields; END ReplObjObtain; PROCEDURESimpleObjObtain (self: SimpleObj; internal: BOOLEAN): REF ObjFields RAISES {ServerError} = VAR lock : BOOLEAN; objMu: Thread.Mutex; BEGIN lock := (NOT internal) AND (self.sync # NIL); IF lock THEN objMu := self.sync.mutex; Thread.Acquire(objMu) END; TRY IF self.protected AND (NOT internal) THEN RaiseServerError("Cannot obtain protected object"); END; RETURN self.fields; FINALLY IF lock THEN Thread.Release(objMu) END; END; END SimpleObjObtain; PROCEDUREReplObjInit (self: ReplObj): ReplObj = BEGIN RETURN self; END ReplObjInit; PROCEDURENewAlias (obj: ValObj; label: TEXT; location: SynLocation.T): ValAlias RAISES {Error, Exception} = VAR hint : INTEGER; hasLabel := FALSE; BEGIN TRY hasLabel := obj.Has(label, (*var*) hint); IF hasLabel THEN RETURN NEW(ValAlias, label := label, labelIndexHint := hint, obj := obj); ELSE RaiseError("Field not found in object: " & label, location); <*ASSERT FALSE*> END; EXCEPT | NetObj.Error (atoms) => RaiseNetException("on remote object access", atoms, location); <*ASSERT FALSE*> | SharedObj.Error (atoms) => RaiseSharedException( "on replicated object access", atoms, location); <*ASSERT FALSE*> | Thread.Alerted => RaiseException(threadAlerted, "on remote object access", location); <*ASSERT FALSE*> END; END NewAlias; PROCEDUREEngineWho (self: RemEngineServer): TEXT RAISES {} = BEGIN RETURN self.who; END EngineWho; PROCEDUREEngineEval (self: RemEngineServer; proc: Val; mySelf: ValObj): Val RAISES {Error, Exception, ServerError} = VAR newEnv : Env; newGlob: GlobalEnv; BEGIN TYPECASE proc OF | ValFun (clos) => IF 1 # clos.fun.bindersNo THEN RaiseServerError( "Engine needs a procedure of 1 argument as argument"); END; newGlob := clos.global; newEnv := NEW(LocalEnv, name := clos.fun.binders.first, val := self.arg, rest := NIL); RETURN ObEval.Term(clos.fun.body, (*in-out*) newEnv, newGlob, mySelf); ELSE RaiseServerError("Engine needs a procedure as argument"); <*ASSERT FALSE*> END; END EngineEval; PROCEDURENewFileSystem (readOnly: BOOLEAN): ValFileSystem = BEGIN RETURN NEW(ValFileSystem, picklable := FALSE, what := "<FileSystem at " & machineAddress & ">", tag := "FileSystem", remote := NEW(RemFileSystemServer, readOnly := readOnly)); END NewFileSystem; PROCEDUREFileSystemIs (self: ValFileSystem; other: ValAnything): BOOLEAN = BEGIN TYPECASE other OF | ValFileSystem (oth) => RETURN self.remote = oth.remote; ELSE RETURN FALSE; END; END FileSystemIs; PROCEDUREFileSystemOpenRead (<*UNUSED*> self : RemFileSystemServer; fileName: TEXT ): Rd.T RAISES {ServerError} = BEGIN TRY RETURN FileRd.Open(fileName); EXCEPT | OSError.E => RaiseServerError("FileSystemOpenRead"); <*ASSERT FALSE*> END; END FileSystemOpenRead; PROCEDUREFileSystemOpenWrite (self: RemFileSystemServer; fileName: TEXT): Wr.T RAISES {ServerError} = BEGIN IF self.readOnly THEN RaiseServerError("FileSystemOpenWrite") END; TRY RETURN FileWr.Open(fileName); EXCEPT | OSError.E => RaiseServerError("FileSystemOpenWrite"); <*ASSERT FALSE*> END; END FileSystemOpenWrite; PROCEDUREFileSystemOpenAppend (self: RemFileSystemServer; fileName: TEXT): Wr.T RAISES {ServerError} = BEGIN IF self.readOnly THEN RaiseServerError("FileSystemOpenAppend") END; TRY RETURN FileWr.OpenAppend(fileName); EXCEPT | OSError.E => RaiseServerError("FileSystemOpenAppend"); <*ASSERT FALSE*> END; END FileSystemOpenAppend; PROCEDURENewProcessor (): ValProcessor = BEGIN RETURN NEW(ValProcessor, picklable := FALSE, tag:="Processor", what := "<Processor at " & machineAddress & ">"); END NewProcessor; PROCEDURERegisterSysCall (name: TEXT; clos: SysCallClosure) = VAR v: Refany.T; BEGIN <* ASSERT sysCallTable # NIL *> IF clos = NIL THEN EVAL sysCallTable.delete(name, (*out*) v); ELSE EVAL sysCallTable.put(name, clos); END; END RegisterSysCall; PROCEDUREFetchSysCall (name: TEXT; VAR (*out*) clos: SysCallClosure): BOOLEAN = VAR v : Refany.T; found: BOOLEAN; BEGIN found := sysCallTable.get(name, (*out*) v); clos := NARROW(v, SysCallClosure); RETURN found; END FetchSysCall;
TYPE
  ObNotifierClosure = NetObjNotifier.NotifierClosure OBJECT
    proc : ValFun;
  OVERRIDES
    notify := ObNotifyMethod;
  END;
PROCEDURE ObNotifyMethod (self: ObNotifierClosure;
                         obj: NetObj.T; st: NetObjNotifier.OwnerState) =
  VAR args   : ARRAY [0..1] OF Val;
  BEGIN
    TYPECASE obj OF
    | RemVar(var) => args[0] := NEW(ValVar, remote := var);
    | RemArray(var) => args[0] := NEW(ValArray, remote := var);
    | RemObj(var) => args[0] := NEW(ValRemObj, remote := var);
    | RemEngine(var) => args[0] := NEW(ValEngine, remote := var);
    | RemFileSystem(var) => args[0] := NEW(ValFileSystem, remote := var);
    ELSE <* ASSERT FALSE *> (* Shouldn't get here! *)
    END;
    CASE st OF
    | NetObjNotifier.OwnerState.Dead =>
      args[1] := NewText("Dead");
    | NetObjNotifier.OwnerState.Failed =>
      args[1] := NewText("Failed");
    END;
    TRY
      EVAL ObEval.Call(self.proc, args);
    EXCEPT
    | Error (packet) => ErrorMsg(SynWr.err, packet);
    | Exception (packet) => ExceptionMsg(SynWr.err, packet);
    END;
  END ObNotifyMethod;
PROCEDURE ObjNotify (val: Val; notifyProc: ValFun) =
  BEGIN
    WITH notifier = NEW(ObNotifierClosure, proc := notifyProc) DO
      TYPECASE val OF
      | ValVar(var) =>        NetObjNotifier.AddNotifier(var.remote, notifier);
      | ValArray(var) =>      NetObjNotifier.AddNotifier(var.remote, notifier);
      | ValRemObj(var) =>     NetObjNotifier.AddNotifier(var.remote, notifier);
      | ValEngine(var) =>     NetObjNotifier.AddNotifier(var.remote, notifier);
      | ValFileSystem(var) => NetObjNotifier.AddNotifier(var.remote, notifier);
      ELSE (* do nothing for other objects *)
      END;
    END;
  END ObjNotify;
 === GC-safe hash table of refanys :-) === 
TYPE TblArr = ARRAY OF RECORD old, new: REFANY END;
REVEAL
  Tbl = BRANDED OBJECT
          a  : REF TblArr;
          top: INTEGER      := 0;
        METHODS
          Get (old: REFANY; VAR (*out*) new: REFANY): BOOLEAN := TblGet;
          Put (old, new: REFANY)                              := TblPut;
        END;
PROCEDURE NewTbl  (): Tbl =
  BEGIN
    RETURN NEW(Tbl, a := NEW(REF TblArr, 256), top := 0);
  END NewTbl;
PROCEDURE TblGet  (self: Tbl; old: REFANY; VAR (*out*) new: REFANY):
  BOOLEAN =
  BEGIN
    FOR i := self.top - 1 TO 0 BY -1 DO
      IF self.a^[i].old = old THEN new := self.a^[i].new; RETURN TRUE END;
    END;
    RETURN FALSE;
  END TblGet;
PROCEDURE TblPut  (self: Tbl; old, new: REFANY) =
  VAR newArr: REF TblArr;
  BEGIN
    self.a^[self.top].old := old;
    self.a^[self.top].new := new;
    INC(self.top);
    IF self.top >= NUMBER(self.a^) THEN
      newArr := NEW(REF TblArr, 2 * NUMBER(self.a^));
      SUBARRAY(newArr^, 0, NUMBER(self.a^)) := self.a^;
      self.a := newArr;
    END;
  END TblPut;
 === Copy === 
TYPE CopyStyle = {ValToVal, ValToLocal, LocalToVal};
TYPE ValVarLocal = Val BRANDED "ValVarLocal" OBJECT val: Val;  END;
TYPE
  ValArrayLocal = Val BRANDED "ValArrayLocal" OBJECT array: REF Vals;  END;
TYPE OrigObjType = {Remote, Replicated, Simple};
TYPE
  ValObjLocal = Val BRANDED "ValObjLocal" OBJECT
                  who                  : TEXT;
                  fields               : REF ObjFields;
                  protected, serialized: BOOLEAN;
                  type                 : OrigObjType;
                END;
PROCEDURE CopyVal  (val: Val; tbl: Tbl; loc: SynLocation.T): Val
  RAISES {Error, SharedObj.Error, NetObj.Error, Thread.Alerted} =
  BEGIN
    RETURN Copy(val, tbl, loc, CopyStyle.ValToVal);
  END CopyVal;
PROCEDURE CopyValToLocal  (val: Val; tbl: Tbl; loc: SynLocation.T): Val
  RAISES {Error, SharedObj.Error, NetObj.Error, Thread.Alerted} =
  BEGIN
    RETURN Copy(val, tbl, loc, CopyStyle.ValToLocal);
  END CopyValToLocal;
PROCEDURE CopyLocalToVal  (val: Val; tbl: Tbl; loc: SynLocation.T): Val
  RAISES {Error, SharedObj.Error, NetObj.Error, Thread.Alerted} =
  BEGIN
    RETURN Copy(val, tbl, loc, CopyStyle.LocalToVal);
  END CopyLocalToVal;
PROCEDURE Copy  (val: Val; tbl: Tbl; loc: SynLocation.T; style: CopyStyle):
  Val RAISES {Error, SharedObj.Error, NetObj.Error, Thread.Alerted} =
  VAR cache: REFANY;
  PROCEDURE CopyFields (fields, newFields: REF ObjFields)
    RAISES {Error, SharedObj.Error, NetObj.Error, Thread.Alerted} =
    BEGIN
      FOR i := 0 TO NUMBER(fields^) - 1 DO
        newFields^[i].label := fields^[i].label;
        newFields^[i].field := Copy(fields^[i].field, tbl, loc, style);
        newFields^[i].update := FALSE;
      END;
    END CopyFields;
  BEGIN
    TYPECASE val OF
    | ValVar (node) =>
        VAR
          newVar     : ValVar;
          newVarLocal: ValVarLocal;
        BEGIN
          IF tbl.Get(node.remote, (*out*) cache) THEN RETURN cache END;
          CASE style OF
          | CopyStyle.ValToVal =>
              newVar := NEW(ValVar, remote := NIL);
              tbl.Put(node.remote, newVar);
              newVar.remote :=
                NEW(RemVarServer,
                    val := Copy(node.remote.Get(), tbl, loc, style));
              RETURN newVar;
          | CopyStyle.ValToLocal =>
              newVarLocal := NEW(ValVarLocal, val := NIL);
              tbl.Put(node.remote, newVarLocal);
              newVarLocal.val := Copy(node.remote.Get(), tbl, loc, style);
              RETURN newVarLocal;
          ELSE                   <*ASSERT FALSE*>
          END;
        END;
    | ValVarLocal (node) =>
        VAR newVar: ValVar;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          CASE style OF
          | CopyStyle.LocalToVal =>
              newVar := NEW(ValVar, remote := NIL);
              tbl.Put(node, newVar);
              newVar.remote :=
                NEW(RemVarServer, val := Copy(node.val, tbl, loc, style));
              RETURN newVar;
          ELSE                   <*ASSERT FALSE*>
          END;
        END;
    | ValOk, ValBool, ValChar, ValText, ValInt, ValReal, ValException,
        ValEngine =>
        RETURN val;
    | ValOption (node) =>
        VAR newOpt: ValOption;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          newOpt := NEW(ValOption, tag := node.tag, val := NIL);
          tbl.Put(node, newOpt);
          newOpt.val := Copy(node.val, tbl, loc, style);
          RETURN newOpt;
        END;
    | ValAlias (node) =>
        VAR newAlias: ValAlias;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          newAlias :=
            NEW(ValAlias, label := node.label,
                labelIndexHint := node.labelIndexHint, obj := NIL);
          tbl.Put(node, newAlias);
          newAlias.obj := Copy(node.obj, tbl, loc, style);
          RETURN newAlias;
        END;
    | ValArray (node) =>
        VAR
          vals, newVals: REF Vals;
          newArr       : ValArray;
          newArrLocal  : ValArrayLocal;
        BEGIN
          IF tbl.Get(node.remote, (*out*) cache) THEN RETURN cache END;
          vals := node.remote.Obtain();
          newVals := NEW(REF Vals, NUMBER(vals^));
          CASE style OF
          | CopyStyle.ValToVal =>
              newArr := NEW(ValArray, remote := NIL);
              tbl.Put(node.remote, newArr);
              FOR i := 0 TO NUMBER(vals^) - 1 DO
                newVals^[i] := Copy(vals^[i], tbl, loc, style);
              END;
              newArr.remote := NEW(RemArrayServer, array := newVals);
              RETURN newArr;
          | CopyStyle.ValToLocal =>
              newArrLocal := NEW(ValArrayLocal, array := NIL);
              tbl.Put(node.remote, newArrLocal);
              FOR i := 0 TO NUMBER(vals^) - 1 DO
                newVals^[i] := Copy(vals^[i], tbl, loc, style);
              END;
              newArrLocal.array := newVals;
              RETURN newArrLocal;
          ELSE                   <*ASSERT FALSE*>
          END;
        END;
    | ValArrayLocal (node) =>
        VAR
          vals, newVals: REF Vals;
          newArr       : ValArray;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          vals := node.array;
          newVals := NEW(REF Vals, NUMBER(vals^));
          CASE style OF
          | CopyStyle.LocalToVal =>
              newArr := NEW(ValArray, remote := NIL);
              tbl.Put(node, newArr);
              FOR i := 0 TO NUMBER(vals^) - 1 DO
                newVals^[i] := Copy(vals^[i], tbl, loc, style);
              END;
              newArr.remote := NEW(RemArrayServer, array := newVals);
              RETURN newArr;
          ELSE                   <*ASSERT FALSE*>
          END;
        END;
    | ValAnything (node) =>
        CASE style OF
        | CopyStyle.ValToVal => RETURN node.Copy(tbl, loc);
        | CopyStyle.ValToLocal, CopyStyle.LocalToVal =>
            IF node.picklable THEN
              RETURN node
            ELSE
              RaiseError("Cannot pickle: " & node.what, loc);
              <*ASSERT FALSE*>
            END;
        ELSE                     <*ASSERT FALSE*>
        END;
    | ValFun (node) =>
        VAR newProc: ValFun;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          newProc := NEW(ValFun, fun := node.fun,
                         global := NEW(REF Vals, NUMBER(node.global^)));
          tbl.Put(node, newProc);
          FOR i := 0 TO NUMBER(node.global^) - 1 DO
            newProc.global^[i] := Copy(node.global^[i], tbl, loc, style);
          END;
          RETURN newProc;
        END;
    | ValMeth (node) =>
        VAR newMeth: ValMeth;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          newMeth := NEW(ValMeth, meth := node.meth,
                         global := NEW(REF Vals, NUMBER(node.global^)));
          tbl.Put(node, newMeth);
          FOR i := 0 TO NUMBER(node.global^) - 1 DO
            newMeth.global^[i] := Copy(node.global^[i], tbl, loc, style);
          END;
          RETURN newMeth;
        END;
    | ValRemObj (node) =>
        VAR
          fields, newFields    : REF ObjFields;
          who                  : TEXT;
          protected, serialized: BOOLEAN;
          sync                 : Sync;
        BEGIN
          IF tbl.Get(node.remote, (*out*) cache) THEN RETURN cache END;
          TRY
            who := node.remote.Who( (*out*)protected, (*out*) serialized);
            fields := node.remote.Obtain(FALSE);
            newFields := NEW(REF ObjFields, NUMBER(fields^));
          EXCEPT
            ServerError (msg) => RaiseError(msg, loc);
          END;
          IF serialized THEN
            sync := NEW(Sync, mutex := NEW(Thread.Mutex))
          ELSE
            sync := NIL
          END;
          CASE style OF
          | CopyStyle.ValToVal =>
              WITH newObj = NEW(ValRemObj, remote := NIL) DO
                tbl.Put(node.remote, newObj);
                CopyFields(fields, newFields);
                WITH newObjServ = NEW(RemObjServer, who := who,
                                      self := newObj, fields := newFields,
                                      protected := protected, sync := sync) DO
                  newObj.remote := newObjServ;
                END;
                RETURN newObj;
              END;
          | CopyStyle.ValToLocal =>
              WITH newObjLocal = NEW(ValObjLocal, who := who,
                                     fields := NIL, protected := protected,
                                     serialized := serialized,
                                     type := OrigObjType.Remote) DO
                tbl.Put(node.remote, newObjLocal);
                CopyFields(fields, newFields);
                newObjLocal.fields := newFields;
                RETURN newObjLocal;
              END;
          ELSE
            <*ASSERT FALSE*>
          END;
        END;
    | ValReplObj (node) =>
        VAR
          fields, newFields: REF ObjFields;
          protected        : BOOLEAN;
          who              : TEXT;
        BEGIN
          IF tbl.Get(node.replica, (*out*) cache) THEN RETURN cache END;
          TRY
            who := node.replica.Who((*out*)protected);
            fields := node.replica.Obtain(FALSE);
            newFields := NEW(REF ObjFields, NUMBER(fields^));
          EXCEPT
            ServerError (msg) => RaiseError(msg, loc);
          END;
          CASE style OF
          | CopyStyle.ValToVal =>
              WITH newObj = NEW(ValReplObj, replica := NIL) DO
                tbl.Put(node.replica, newObj);
                CopyFields(fields, newFields);
                WITH newObjServ = NEW(ReplObjStd, who := who,
                                      self := newObj,
                                      protected := protected,
                                      fields := newFields).init() DO
                  newObj.replica := newObjServ;
                END;
                RETURN newObj;
              END;
          | CopyStyle.ValToLocal =>
              WITH newObjLocal = NEW(
                                   ValObjLocal, who := who, fields := NIL,
                                   protected := protected,
                                   serialized := FALSE,
                                   type := OrigObjType.Replicated) DO
                tbl.Put(node.replica, newObjLocal);
                CopyFields(fields, newFields);
                newObjLocal.fields := newFields;
                RETURN newObjLocal;
              END;
          ELSE
            <*ASSERT FALSE*>
          END;
        END;
    | ValSimpleObj (node) =>
        VAR
          fields, newFields    : REF ObjFields;
          who                  : TEXT;
          protected, serialized: BOOLEAN;
          sync                 : Sync;
        BEGIN
          IF tbl.Get(node.simple, (*out*) cache) THEN RETURN cache END;
          TRY
            who := node.simple.Who( (*out*)protected, (*out*) serialized);
            fields := node.simple.Obtain(FALSE);
            newFields := NEW(REF ObjFields, NUMBER(fields^));
          EXCEPT
            ServerError (msg) => RaiseError(msg, loc);
          END;
          IF serialized THEN
            sync := NEW(Sync, mutex := NEW(Thread.Mutex))
          ELSE
            sync := NIL
          END;
          CASE style OF
          | CopyStyle.ValToVal =>
              WITH newObj = NEW(ValSimpleObj, simple := NIL) DO
                tbl.Put(node.simple, newObj);
                CopyFields(fields, newFields);
                WITH newObjServ = NEW(SimpleObj, who := who, self := newObj,
                                      fields := newFields,
                                      protected := protected, sync := sync) DO
                  newObj.simple := newObjServ;
                END;
                RETURN newObj;
              END;
          | CopyStyle.ValToLocal =>
              WITH newObjLocal = NEW(ValObjLocal, who := who,
                                     fields := NIL, protected := protected,
                                     serialized := serialized,
                                     type := OrigObjType.Simple) DO
                tbl.Put(node.simple, newObjLocal);
                CopyFields(fields, newFields);
                newObjLocal.fields := newFields;
                RETURN newObjLocal;
              END;
          ELSE
            <*ASSERT FALSE*>
          END;
        END;
    | ValObjLocal (node) =>
        VAR
          fields, newFields: REF ObjFields;
          sync             : Sync;
        BEGIN
          IF tbl.Get(node, (*out*) cache) THEN RETURN cache END;
          fields := node.fields;
          newFields := NEW(REF ObjFields, NUMBER(fields^));
          IF node.serialized THEN
            sync := NEW(Sync, mutex := NEW(Thread.Mutex))
          ELSE
            sync := NIL
          END;
          CASE style OF
          | CopyStyle.LocalToVal =>
              CASE node.type OF
              | OrigObjType.Remote =>
                  WITH newObj = NEW(ValRemObj, remote := NIL) DO
                    tbl.Put(node, newObj);
                    CopyFields(fields, newFields);
                    WITH newObjServ = NEW(RemObjServer, who := node.who,
                                          self := NIL, fields := newFields,
                                          protected := node.protected,
                                          sync := sync) DO
                      newObj.remote := newObjServ;
                      newObjServ.self := newObj;
                    END;
                    RETURN newObj;
                  END;
              | OrigObjType.Replicated =>
                  WITH newObj = NEW(ValReplObj, replica := NIL) DO
                    tbl.Put(node, newObj);
                    CopyFields(fields, newFields);
                    WITH newObjServ = NEW(
                                        ReplObjStd, who := node.who,
                                        self := NIL,
                                        protected := node.protected,
                                        fields := newFields).init() DO
                      newObj.replica := newObjServ;
                      newObjServ.self := newObj;
                      RETURN newObj;
                    END;
                  END;
              | OrigObjType.Simple =>
                  WITH newObj = NEW(ValSimpleObj, simple := NIL) DO
                    tbl.Put(node, newObj);
                    CopyFields(fields, newFields);
                    WITH newObjServ = NEW(SimpleObj, who := node.who,
                                          self := NIL, fields := newFields,
                                          protected := node.protected,
                                          sync := sync) DO
                      newObj.simple := newObjServ;
                      newObjServ.self := newObj;
                      RETURN newObj;
                    END;
                  END;
              END;
          ELSE                   <*ASSERT FALSE*>
          END;
        END;
    ELSE                         <*ASSERT FALSE*>
    END;
  END Copy;
PROCEDURE CopyId  (           self: ValAnything;
                  <*UNUSED*> tbl : Tbl;
                  <*UNUSED*> loc : SynLocation.T): ValAnything =
  BEGIN
    RETURN self;
  END CopyId;
PROCEDURE CopyError  (           self: ValAnything;
                     <*UNUSED*> tbl : Tbl;
                                loc : SynLocation.T): ValAnything
  RAISES {Error} =
  BEGIN
    RaiseError("Cannot copy: " & self.what, loc); <*ASSERT FALSE*>
  END CopyError;
--------------------Pickling routines-----------------------
 Need a pickle special for the simple objects, and a shared
   object special for the replicated objects 
PROCEDURE-- This was an attempt to convince the NetObj runtime to do the right thing on pickling. Has been replaced by the current obliq pickling code, using Copy.WriteFields (out: Pickle.Writer; fields: REF ObjFields; <*UNUSED*> pkl: REF ObjFields) RAISES {Pickle.Error, Wr.Failure, Thread.Alerted} = BEGIN PickleStubs.OutInteger(out, NUMBER(fields^)); PickleStubs.OutRef(out, fields); END WriteFields; PROCEDUREReadFields (in: Pickle.Reader; <*UNUSED*> pkl: REF ObjFields): REF ObjFields RAISES {Pickle.Error, Rd.EndOfFile, Rd.Failure, Thread.Alerted} = VAR ret : REF ObjFields; BEGIN WITH num = PickleStubs.InInteger(in) DO ret := PickleStubs.InRef(in); <* ASSERT num = NUMBER(ret^) *> END; RETURN ret; END ReadFields; TYPE SimpleObjSpecial = Pickle.Special OBJECT OVERRIDES write := Write_SimpleObj; read := Read_SimpleObj; END; PROCEDUREWrite_SimpleObj (<*UNUSED*>ts: SimpleObjSpecial; ref: REFANY; out: Pickle.Writer) RAISES {Pickle.Error, Wr.Failure, Thread.Alerted} = VAR o := NARROW(ref, SimpleObj); tc := TYPECODE(ref); BEGIN IF tc = TYPECODE(SimpleObj) THEN PickleStubs.OutText(out, o.who); IF o.sync # NIL THEN PickleStubs.OutBoolean(out, TRUE); ELSE PickleStubs.OutBoolean(out, FALSE); END; PickleStubs.OutBoolean(out, o.protected); PickleStubs.OutRef(out, o.self); PickleStubs.OutRef(out, o.pickleIn); PickleStubs.OutRef(out, o.pickleOut); WriteFields(out, o.fields, o.pickleOut); ELSE RAISE Pickle.Error("Pickle.Error: cannot handle subtypes " & "of ObValue.SimpleObj"); END; END Write_SimpleObj; PROCEDURERead_SimpleObj (<*UNUSED*>ts: SimpleObjSpecial; in: Pickle.Reader; id: Pickle.RefID):REFANY RAISES {Pickle.Error, Rd.EndOfFile, Rd.Failure, Thread.Alerted} = VAR o := NEW(SimpleObj); BEGIN in.noteRef(o, id); o.who := PickleStubs.InText(in); IF PickleStubs.InBoolean(in) THEN o.sync := NEW(Sync, mutex := NEW(Thread.Mutex)) END; o.protected := PickleStubs.InBoolean(in); o.self := PickleStubs.InRef(in); o.pickleIn := PickleStubs.InRef(in); o.pickleOut := PickleStubs.InRef(in); o.fields := ReadFields(in, o.pickleIn); RETURN o; END Read_SimpleObj; TYPE ReplObjStdSpecial = ObValuePickle.ReplObjStdSpecial OBJECT OVERRIDES write := Write_ReplObjStd; read := Read_ReplObjStd; END; PROCEDUREWrite_ReplObjStd (<*UNUSED*>ts: ReplObjStdSpecial; ref: SharedObj.T; out: Pickle.Writer) RAISES {Pickle.Error, Wr.Failure, Thread.Alerted} = VAR obj := NARROW(ref, ReplObjStd); BEGIN PickleStubs.OutRef(out, obj.who); PickleStubs.OutRef(out, obj.self); PickleStubs.OutInteger(out, ORD(obj.protected)); PickleStubs.OutRef(out, obj.pickleIn); PickleStubs.OutRef(out, obj.pickleOut); WriteFields(out, obj.fields, obj.pickleOut); END Write_ReplObjStd; PROCEDURERead_ReplObjStd (<*UNUSED*>ts: ReplObjStdSpecial; ref: SharedObj.T; in: Pickle.Reader) RAISES {Pickle.Error, Rd.EndOfFile, Rd.Failure, Thread.Alerted} = VAR obj := NARROW(ref, ReplObjStd); BEGIN obj.who := PickleStubs.InRef(in, -1); obj.self := PickleStubs.InRef(in, TYPECODE(ValReplObj)); obj.protected := VAL(PickleStubs.InInteger(in, 0,1), BOOLEAN); obj.pickleIn := PickleStubs.InRef(in, -1); obj.pickleOut := PickleStubs.InRef(in, -1); obj.fields := ReadFields(in, obj.pickleIn); END Read_ReplObjStd; TYPE InhibitSpecial = Pickle.Special OBJECT reason: TEXT; OVERRIDES write := WriteInhibitTransmission; read := ReadInhibitTransmission; END; PROCEDUREWriteInhibitTransmission ( self: InhibitSpecial; <*UNUSED*> ref : REFANY; <*UNUSED*> wr : Pickle.Writer ) RAISES {Pickle.Error} = BEGIN RAISE Pickle.Error(self.reason); END WriteInhibitTransmission; PROCEDUREReadInhibitTransmission ( self: InhibitSpecial; <*UNUSED*> rd : Pickle.Reader; <*UNUSED*> id : Pickle.RefID ): REFANY RAISES {Pickle.Error} = BEGIN RAISE Pickle.Error(self.reason); END ReadInhibitTransmission; PROCEDUREInhibitTransmission (tc: INTEGER; reason: TEXT) = BEGIN Pickle.RegisterSpecial(NEW(InhibitSpecial, sc := tc, reason := reason)); END InhibitTransmission; BEGIN Pickle.RegisterSpecial(NEW(SimpleObjSpecial, sc := TYPECODE(SimpleObj))); ObValuePickle.RegisterSpecial_ReplObjStd(NEW(ReplObjStdSpecial)); END ObValue.
There should be a way to temporarily register specials for NetObj.T's. The array of specials should be a parameter to Pickle.Read/Pickle.Write.
In Setup: Pickle.RegisterSpecial(NEW(ValArraySpecial, sc:=TYPECODE(ValArray)));
TYPE ValArraySpecial = Pickle.Special OBJECT OVERRIDES write := WriteValArray; read := ReadValArray; END;
PROCEDURE WriteValArray(self: ValArraySpecial; ref: REFANY; wr: Pickle.Writer) RAISES {Pickle.Error, Wr.Failure, Thread.Alerted} = BEGIN TYPECASE ref OF
      ValArray(valArray) =>
      
TYPECASE valArray.remote OF
      
      RemArrayServer(remArrayServer) =>
          
wr.write(remArrayServer.array);
      ELSE RAISE Wr.Failure(NIL);
      END;
    ELSE RAISE Wr.Failure(NIL);
    END;
  END WriteValArray;
PROCEDURE ReadValArray(self: ValArraySpecial; rd: Pickle.Reader; id: Pickle.RefID): REFANY RAISES {Pickle.Error, Rd.EndOfFile, Rd.Failure, Thread.Alerted} = VAR res: ValArray; BEGIN res := NEW(ValArray, remote := NEW(RemArrayServer, array := NIL)); rd.noteRef(res, id); NARROW(res.remote, RemArrayServer).array := rd.read(); RETURN res; END ReadValArray;