Oberon ||
Library ||
Module Index ||
Search Engine ||
Definition ||
Module
Ulm's Oberon Library:
PersistentObjects
NAME
PersistentObjects - abstraction for persistent objects
SYNOPSIS
CONST fullTypeName = 1; typeCode = 2; incrTypeCode = 3;
CONST withSize = 4; withoutSize = 0;
CONST withHier = 8; withoutHier = 0;
TYPE Mode = SHORTINT;
TYPE Object = POINTER TO ObjectRec;
TYPE ObjectRec = RECORD (Services.ObjectRec) END;
TYPE ReadProc = PROCEDURE (s: Streams.Stream; o: Object) : BOOLEAN;
TYPE WriteProc = PROCEDURE (s: Streams.Stream; o: Object) : BOOLEAN;
TYPE CreateProc = PROCEDURE (VAR o: Object);
TYPE CreateAndReadProc = PROCEDURE (s: Streams.Stream;
create: BOOLEAN;
VAR o: Object) : BOOLEAN;
TYPE Interface = POINTER TO InterfaceRec;
TYPE InterfaceRec =
RECORD
(Objects.ObjectRec)
create: CreateProc; (* create object *)
read: ReadProc; (* read data from stream *)
write: WriteProc; (* write data to stream *)
createAndRead: CreateAndReadProc; (* replaces create & read *)
END;
CONST cannotReadData = 0;
CONST cannotWriteData = 1;
CONST cannotReadType = 2;
CONST cannotWriteType = 3;
CONST invalidType = 4;
CONST unknownType = 5;
CONST otherTypeHier = 6;
CONST eofReached = 7;
CONST cannotSkip = 8;
CONST typeGuardFailure = 9;
CONST errorcodes = 10;
TYPE ErrorCode = SHORTINT;
TYPE Event = POINTER TO EventRec;
TYPE EventRec =
RECORD
(Events.EventRec)
stream: Streams.Stream;
errorcode: ErrorCode;
END;
VAR errormsg: ARRAY errorcodes OF Events.Message;
VAR error: Events.EventType;
PROCEDURE RegisterType(VAR type: Services.Type;
name, baseName: ARRAY OF CHAR;
if: Interface);
PROCEDURE Init(object: Object; type: Services.Type);
PROCEDURE SetMode(s: Streams.Stream; mode: Mode);
PROCEDURE GetMode(s: Streams.Stream; VAR mode: Mode);
PROCEDURE IsProjected(object: Object) : BOOLEAN;
PROCEDURE Read(s: Streams.Stream; VAR object: Object) : BOOLEAN;
PROCEDURE Write(s: Streams.Stream; object: Object) : BOOLEAN;
PROCEDURE ReadObjectOrNIL(s: Streams.Stream; VAR object: Object) : BOOLEAN;
PROCEDURE WriteObjectOrNIL(s: Streams.Stream; object: Object) : BOOLEAN;
PROCEDURE ReadInto(s: Streams.Stream; object: Object) : BOOLEAN;
PROCEDURE GuardedRead(s: Streams.Stream; guard: Services.Type;
VAR object: Object) : BOOLEAN;
PROCEDURE GuardedReadObjectOrNIL(s: Streams.Stream; guard: Services.Type;
VAR object: Object) : BOOLEAN;
DESCRIPTION
PersistentObjects
defines an extensible abstraction for persistent
objects and implements input and output operations for them
(i.e. transforming the object into a sequence of bytes
and vice versa).
A persistent object is a data structure whose existence
transcends time
(i.e. the object continues to exist after
its creator ceases to exist)
and/or space
(i.e. the object's location moves from the address space in which
it was created).
Defining persistent objects
An interface defines a set of procedures which implement a
PersistentObjects-abstraction for
a specific extension of PersistentObjects.Object.
Not all interface procedures must be given,
some of them may be set to NIL:
- The whole interface may be passed as NIL
to RegisterType for abstract objects which
are never instantiated,
do not carry any data to be saved and restored,
and serve only as base type for other extensions.
- Empty extensions which may be instantiated but
don't carry any data need just create,
all other interface procedures are then to be set to NIL.
- The most common variant is to provide create,
read, and write.
In this case, createAndRead must be passed as NIL.
- Abstract extensions that are never instantiated may
set create and createAndRead to NIL while
defining read and write.
- Modules which filter all operations of
PersistentObjects for a specific type
need to implement create and read
as a joined operation which allows the creation
to be delayed to the reading time.
This allows the read object to be of a different
type than the object written.
This technique is for example used
by RemotePersistentObjects which causes
written proxy objects to be read as original objects.
In this case, createAndRead and
write are to be provided
with create and read set to NIL.
Types which support createAndRead must not
be extended and are better kept private for this reason.
The interface procedures should meet following specification:
- create: PROCEDURE(VAR o: Object);
-
create (via NEW) a new object of the specific type
and perform the type-specific initializations for that
object which includes the call of Init for it.
Note that Services.Init is called by Init
and that initializations of extensions between
PersistentObjects.Object and the specific extension
should be done in the extension order
after calling Init.
- read: PROCEDURE(s: Streams.Stream; o: Object) : BOOLEAN;
-
read the extension-specific data of o from s.
Note that one call of PersistentObjects.Read
causes read to be called for each non-abstract
extension between PersistentObjects.Object
and the type of the object.
- write: PROCEDURE(s: Streams.Stream; o: Object) : BOOLEAN;
-
write the extension-specific data of o to s.
Note that if createAndRead is specified instead of
create and read, write must write
the data of all extensions and not only that of
the specific extension.
- createAndRead: PROCEDURE(s: Streams.Stream; create: BOOLEAN; VAR o: Object) : BOOLEAN;
-
perform the operations of create (if requested)
and read.
Note that in difference to read the interface procedure
createAndRead is responsible for reading the
whole object covering all extensions.
In some cases there exist several variants
for the interface procedures write and read
to save and load extension-specific data,
e.g. a deep copy with all depending objects, or a
copy of the object only with further references exported
(see RemoteObjects).
Additional parameterization may be achieved
by attaching disciplines
(see Disciplines) to the output stream
and by examining them in the write interface procedure.
But please note that PersistentObjects is free to pass
temporary in-memory streams to the interface procedures.
To allow discipline access in this case
PersistentObjects links the temporary stream
via IndirectDisciplines.Forward to the original stream.
Thus, IndirectDisciplines.Seek has to be used instead of
Disciplines.Seek to find additional parameters inside of
the interface procedures.
The interface procedures should read and write Oberon base types
by the use of NetIO.
This assures that the exchange of persistent objects
is even possible between different hardware architectures.
The interface procedures are free to call Read and Write
for persistent subobjects.
In case of deep copies of possibly circular data structures
the use of LinearizedStructures is recommended.
RegisterType
is to be called
during the initialization time
by modules which extend
PersistentObjects.Object.
The type names to be given should be of the form
"ModuleName.TypeName"
(where
ModuleName
is the name of the module the data type is defined in, and
TypeName
is the name of the data type)
to assure uniqueness and to support dynamic loading.
If the
data type of an object is a direct extension
of PersistentObjects.Object, "" is
to be given as baseName.
Types which have createAndRead as
interface procedure must not be given as base type.
Init
connects a newly created object
with its data type given by type.
Note that Init calls Services.Init for object.
Using persistent objects
Persistent objects may be saved and loaded in dependence of
a stream-specific mode.
By default, each object saved is accompanied by
a full type description which includes the whole type hierarchy
of that object.
This allows each object to be read in independently from
others and makes projections possible.
Projections are necessary if one of the modules
which belongs to the type hierarchy is not present
and cannot be loaded dynamically.
For many applications, however, this full type information
takes too much space and needs too long to be read in.
PersistentObjects supports an incremental
type coding mode which leads to very compact coding
for object sequences with repeating types.
But this requires that objects are read in the
same sequence as they have been written earlier.
Modes are to be given as the sum of three integer constants which
select three different submodes:
- fullTypeName, typeCode, or incrTypeCode
specify how types are to be encoded.
fullTypeName encodes types by giving the
type names which has been earlier given to RegisterType,
typeCode emits just type numbers which are not
compatible to other programs, and
incrTypeCode enables an efficient incremental encoding
(as described above)
which preserves compatibility to other programs.
- withSize or withoutSize specify
whether the size of the whole object saved will be
stored (at the beginning) or not.
The reading party is able to skip unsupported parts
of the object in case of projections if the size
is part of the encoding.
Note that size informations are expensive to generate
if a stream is used which does not support seek operations.
- withHier or withoutHier specify
whether the whole type hierarchy is to be encoded or just
the exact type of the object only.
Projections are only possible if the type hierarchy
is supported.
The default mode is fullTypeName +
withSize + withHier,
a more efficient variant for sequential access is
incrTypeCode + withSize + withHier,
and the most efficient variant which still supports
exchangeability with other programs but
prohibits projections is
incrTypeCode + withoutSize + withoutHier.
SetMode
sets the mode for s and
GetMode
returns the mode of s which is currently in use.
Note that the mode affects write operations only.
IsProjected
allows to test whether object
was projected during its read operation or not.
Write
converts object into a sequence of bytes which
is written to s which may be later read in by Read.
Note that Write requires object to be non-NIL
and that Read guarantees object to be non-NIL
on success.
GuardedRead works like Read but applies
a type guard to the object read in and returns FALSE
if the read object is not an extension of guard.
Note that in case of type guard failures the read object
is not assigned to object to avoid hard
type guard failures (i.e. those which would lead
to runtime errors which at least abort the current coroutine).
To differentiate between type guard failures
(which leave the stream at a defined position) and
I/O errors (which possibly leave the stream at an undefined
position) it may be useful to examine the error events
which have been passed to s.
WriteObjectOrNIL
and
ReadObjectOrNIL
work like Write and Read but allow NIL
to be passed and to be returned.
Note that ReadObjectOrNIL returns TRUE
even when NIL has been successfully passed.
GuardedReadObjectOrNIL works like
GuardedRead but allows like ReadObjectOrNIL
NIL to be returned.
Because NIL may always be assigned to an object
reference, the type test is skipped in case of NIL
and TRUE is returned.
Note that read and write operations must be properly paired, i.e.
the XXXOrNIL operations are not compatible
to the other operations.
ReadInto allows to avoid the creation of a new
object by reading into an already existing object.
This requires the type of the object to be read in
to be an extension of object.
ReadInto is compatible to Write only.
DIAGNOSTICS
All read and write operations return FALSE
in case of errors and generate an error event in that
case which is related to the given stream.
Following error codes are implemented:
- cannotReadData
-
while it was possible to read and decode the type,
a read error occurred during reading of the object data,
i.e. one of the read interface procedures returned
FALSE.
Note that reading is immediately aborted in such a case
which leaves the stream at an undefined position
(possibly inside of an object).
- cannotWriteData
-
while it was possible to encode and write the type information,
a write error occurred during writing of the object data,
i.e. one of the write interface procedures returned
FALSE.
Note that writing is immediately aborted in such a case
which leaves the stream at an undefined position.
- cannotReadType
-
is returned in case of failed stream operations only
during reading the type information.
- cannotWriteType
-
a write operation for the underlying stream failed.
- invalidType
-
bogus input was found which does not conform
to a valid type information.
This may happen due to undefined stream positions
or due to incompatible versions of PersistentObjects.
Another common source of this problem are unpaired
read and write operations (e.g. writing an object
with WriteObjectOrNIL but trying to read it
with Read).
- unknownType
-
a valid type information was found which,
however, is not known or supported by the reading program
and does not permit projections.
This leaves the stream at a defined position only
if withSize was given at the time of writing.
- otherTypeHier
-
is returned in case of incremental type informations
which include type hierarchies if inconsistencies have
been found.
Inconsistencies may result from non-sequential
write or read operations, or from different writing sources.
- eofReached
-
end of file was unexpectedly encountered.
This may be returned during reading of the type information
or during the reading of the object data.
- cannotSkip
-
a projection was attempted and failed projections
were prohibited (due to missing size information) or
the non-supported data parts could not be skipped.
Nevertheless, the so far created and read object is
returned in object
despite the return value of FALSE.
- typeGuardFailure
-
is returned by GuardedRead or GuardedReadObjectOrNIL
if the read object is not an extension of guard,
or by ReadInto
if the object to be read
has not a type which is an extension of that of object.
Several errors which result from programming mistakes
are covered by assertions:
- RegisterType and Services.InitType
(which is called by RegisterType)
check all parameters for validity,
i.e. name must be non-empty and unique,
baseName must be empty or an already known
name of a type which has been passed to
RegisterType (not to Service.CreateType),
and if must be NIL (for abstract types)
or contain a valid combination of supported interface
procedures as described above.
Types which have createAndRead as
interface procedure must not be given as base type.
- Init must not be called for objects
of abstract types (i.e. those which must not be instantiated)
and type must be one of type which has
been earlier returned by RegisterType.
- Invalid mode values which are passed to SetMode
lead to failures on subsequent write operations.
SEE ALSO
- ConstantObjects
-
simplified support of PersistentObjects for constant objects
- Containers
-
collections of persistent objects
- Disciplines
-
attachment of non-persistent data structures
- IndirectDisciplines
-
shared disciplines
- LinearizedStructures
-
I/O of possibly circular data structures
- ModularizedStructures
-
distribution of graphs of persistent objects across multiple byte sequences
- NetIO
-
I/O of Oberon base types
- PersistentDisciplines
-
persistent disciplines
- PersistentEvents
-
support of persistent events
- PersistentTexts
-
persistent objects representing texts
- RelatedEvents
-
error handling
- RemotePersistentObjects
-
support of PersistentObjects for proxy objects
which have been returned by RemoteObjects
- Services
-
type system of the library
- Streams
-
stream operations
AUTHORS
The original implementation and manual page was written 1993
by Detlef Birkholz.
The revisions are due to Andreas Borchert.
Edited by: borchert, last change: 2003/07/10, revision: 1.16, converted to HTML: 2003/07/10
Oberon ||
Library ||
Module Index ||
Search Engine ||
Definition ||
Module