SIMPLE COMPONENTS

version 4.51

by Dmitry A. Kazakov

(mailbox@dmitry-kazakov.de)



This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As a special exception, if other files instantiate generics from this unit, or you link this unit with other files to produce an executable, this unit does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Public License.

The current version provides implementations of smart pointers, directed graphs, sets, maps, B-trees, stacks, tables, string editing, unbounded arrays, expression analyzers, lock-free data structures, synchronization primitives (events, race condition free pulse events, arrays of events, reentrant mutexes, deadlock-free arrays of mutexes), pseudo-random non-repeating numbers, symmetric encoding and decoding, IEEE 754 representations support, streams, multiple connections server/client designing tools and protocols implementations. It grew out of needs and does not pretend to be universal. Tables management and strings editing are described in separate documents see Tables and Strings edit. The library is kept conform to the Ada 95, Ada 2005, Ada 2012 language standards.

Quick reference

See also changes log.

1. Objects and handles (smart pointers)

The objects and handles are designed to provide automatic garbage collection. The objects are created explicitly, but never explicitly destroyed. An application program usually should not directly access objects, using object handles (smart pointers) instead. As long as at least one handle to an object exists the object will not be destroyed. When the last handle disappears the object is automatically destroyed. The presented implementation is oriented on large and rather complex objects. Usually it has little sense to have pointers to small objects, having no identity. For such objects by-value semantics is often safer, easier to understand and more efficient. For this reason an object-oriented approach was chosen. The object type is considered a descendant of a limited controlled type which can be extended as necessary. Same handle type can be used for the whole class of descendant types. The proxy operations can be defined on handles which implementations may dispatch according to the actual type of the pointed object.

A specialization of objects is provided to support object's persistence. Such objects can be stored in an external persistent storage and then restored from there. The persistent storage interface itself is an object. This allows implementation of object serving as proxies of external objects permanently resident in an external storage.

1.1. Objects

The package Object provides the base type Entity for all objects:

type Entity is new

Ada.Finalization.Limited_Controlled with

record

Use_Count : Natural := 0 ;

end record ;

type Entity_Ptr is access all Entity'Class;

It is a limited controlled type. The following operations are defined on it:

procedure Decrement_Count

( Object : in out Entity;

Use_Count : out Natural

);

This procedure decreases object's reference count. The output parameter Use_Count is the updated value of the reference count to be used instead of Object.Use_Count in order to avoid race condition. It should never be used explicitly, except than in implementations of handles to objects.

function Equal

( Left : Entity;

Right : Entity'Class;

Flag : Boolean := False

) return Boolean;

function Less

( Left : Entity;

Right : Entity'Class;

Flag : Boolean := False

) return Boolean;

These functions are used to compare objects. The meaning of comparison is usually defined by the nature of the objects. However the main reason why comparison is defined, is to support ordered sets of objects, so any order is suitable. Thus the implementations of Equal and Less use storage addresses to get Entity objects ordered. They should be overridden if a more meaningful order of objects exists. Note that Ada does not fully support multiple dispatch. Therefore the operations are declared asymmetric. The second parameter is class-wide. If the operation is overridden, an implementation should dispatch on the second parameter to emulate true multiple dispatch. The parameter Flag indicates whether the function is called recursively. The following code fragment illustrates how to do it:

function Less

( Left : A_New_Object_Type;

Right : Object.Entity'Class;

Flag : Boolean := False

) return Boolean is

begin

if ( Flag

or else

Right not in A_New_Object_Type'Class

or else

Right in A_New_Object_Type

)

then

-- Implement it here

...

else

-- Dispatch on the second parameter

return

not ( Less (Right, Left, True)

or else

Equal (Right, Left, True)

);

end if ;

end Less;

The idea is that a given overriding is responsible for implementation of Less if and only if Left :> Right, i.e. when Left is in the class of Right. The dispatching mechanism warranties that Left is in the type class, so if Right is of the same type or else does not belong to the type class, then Left :> Right. Otherwise, Right is used to re-dispatch and Flag is set to indicate that no more dispatch may happen. Observe, that if Left and Right are siblings and therefore neither of Left :> Right and Left <: Right is true, then Flag will stop the recursion.

If the implementation casts Right down to a known type, as it usually would do in other cases, then in the case of siblings, this would cause propagation of Constraint_Error out of Less or Equal. If this behavior is undesirable, another way to deal with comparison of siblings is to find the most specific common ancestor of both. In that case the code of Less might look as follows:

function Less

( Left : A_New_Object_Type;

Right : Object.Entity'Class;

Flag : Boolean := False

) return Boolean is

begin

if ( Right not in A_New_Object_Type'Class

or else

Right in A_New_Object_Type

)

then

-- Implement it here

...

elsif Flag then

-- Using Less of the most specific common ancestor,

-- for example, the predefined Less:

return Object.Less (Object.Entity (Left), Right, True);

else

-- Dispatch on the second parameter

return

not ( Less (Right, Left, True)

or else

Equal (Right, Left, True)

);

end if ;

end Less;

procedure Finalize (This : in out Entity);

This procedure is called upon object finalization. It raises Program_Error if the destroyed object is still in use. Note that any derived type shall call this procedure from its implementation of Finalize when it overrides Finalize.

procedure Increment_Count (Object : in out Entity);

This procedure increases object's reference count. It should never be used explicitly, except than in implementations of handles to objects.

procedure Initialize (Object : in out Entity);

This procedure is called upon object initialization. Any derived type shall call it from its implementation of.

procedure Release (Ptr : in out Entity_Ptr);

The object pointed by Ptr is deleted if its use count in 1. Otherwise the use count is decremented. Ptr becomes null if the object it points to is deleted. The procedure does nothing if Ptr is already null . It can be used for implementation of the smart pointers to Entity and its descendants.

1.1.1. Tasking

The package provides several implementations of Object:

Task-safe implementation allows use of Decrement_Count, Increment_Count and Release from concurrent tasks. This makes handles to objects task-safe. Note that this by no means makes the objects themselves task-safe. It is up to the implementation of the derived type to ensure safety of its operations.

implementation allows use of Decrement_Count, Increment_Count and Release from concurrent tasks. This makes handles to objects task-safe. Note that this by no means makes the objects themselves task-safe. It is up to the implementation of the derived type to ensure safety of its operations. Unsafe implementation can be found in the subdirectory single-task . The source files there replace files with the corresponding names in the parent directory. The project files for GNAT Ada allow choice of the implementation per project scenario variable Tasking .

implementation can be found in the subdirectory . The source files there replace files with the corresponding names in the parent directory. The project files for GNAT Ada allow choice of the implementation per project scenario variable . Tracing implementation is intended for use with GNAT Ada. The corresponding files provide run-time tracing of reference counts. The implementation is located in the subdirectory gnat-debug. Upon an error the source locations of the object creation, of the reference count changes, where the error was detected are dumped onto the standard output, supplied by the stack traceback at the location When the project is compiled manually, -bargs -E switches must be used with gnatmake .

1.2. Handles to objects

The generic child package Object.Handle defines the type Handle used to access objects of a given type:

generic

type Object_Type (<>) is abstract new Entity with private ;

type Object_Type_Ptr is access Object_Type'Class;

package Object.Handle is

type Handle is new Ada.Finalization.Controlled with private ;

The package has two generic parameters:

Object_Type is a type derived from Entity.

is a type derived from Entity. Object_Type_Ptr is an access type to class-wide objects of Object_Type.

Handles can be assigned to copy a reference to the object. If a handle object is not initialized it is invalid. An invalid handle cannot be used to access objects, but it can be used in some comparisons, it can be copied and assigned. The constant Null_Handle defined in the package is a predefined invalid handle. The following operations are defined on a Handle:

procedure Finalize (Reference : in out Handle);

The destructor destroys the referenced object (if any) in case when the handle was the last one pointing the object.

procedure Invalidate (Reference : in out Handle);

This procedure detaches handle from the object (if any) it points to. The result handle cannot be used to access any object. The referenced object is destroyed if it was the last handle.

function Is_Valid (Reference : Handle) return Boolean;

function Ptr (Reference : Handle) return Object_Type_Ptr;

function Ref (Thing : Object_Type_Ptr) return Handle;

This function is used to get a handle from a pointer to an object.

procedure Set (Reference : in out Handle; Thing : Object_Type_Ptr);

This procedure resets the handle Reference to a possibly another object. In the course of this operation the previously pointed object may be destroyed if Reference was the last handle pointing to it. It is safe when Thing is the object Reference already points to. When Thing is null, this procedure is equivalent to Invalidate.

function " < " (Left, Right : Handle) return Boolean;

function " <= "(Left, Right : Handle) return Boolean;

function " >= "(Left, Right : Handle) return Boolean;

function " > " (Left, Right : Handle) return Boolean;

function " = " (Left, Right : Handle) return Boolean;

function " = "

( Left : Handle;

Right : access Object_Type'Class

) return Boolean;

function " = "

( Left : access Object_Type'Class;

Right : Handle

) return Boolean;

Valid handles are comparable. The result of comparison is one of the objects they point to. Implementations of the comparisons use Less and Equal defined on Object_Type. If one of the parameters is invalid Contraint_Error is propagated for all functions except " = ". For equality (and thus inequality) it is legal to compare with an invalid handle. The result of such comparison is true if and only if both handles are invalid. One of parameters in equality is allowed to be a pointer to an object.

1.3. An example of use

The usage of objects and handles is illustrated by the following simplified example of an implementation of dynamic strings:

with Object;



package Test_My_String is

type My_String (Length : Natural) is

new Object.Entity with record

Value : String (1..Length);

end record ;

type My_String_Ptr is access My_String'Class;

end Test_My_String;

An instance of My_String keeps the string body. But a user should rather use handles to My_String, provided by the child package:

with Object.Handle;



package Test_My_String.Handle is

--

-- Though an instantiation of Object.Handle provides handles to

-- My_String, we would like to have some additional operations on

-- handles.

--

package My_String_Handle is

new Object.Handle (My_String, My_String_Ptr);

--

-- So we immediately derive from the obtained type. Note that no

-- additional components needed (with null record).

--

type My_Safe_String is

new My_String_Handle.Handle with null record ;

--

-- Now define useful operations on string handles:

--

function Create (Value : String) return My_Safe_String;

function Value (Reference : My_Safe_String) return String;

--

-- Note that Copy takes handle as an inout-parameter. It does not touch

-- the old object it just creates a new one and sets handle to point to

-- it. The old object is automatically destroyed if no more referenced.

--

procedure Copy

( Reference : in out My_Safe_String;

New_Value : String

);

procedure Copy

( Reference : in out My_Safe_String;

New_Value : My_Safe_String

);

private

--

-- Note that Ref shall be overridden. This is a language requirement,

-- which ensures that the results are covariant. We make it private

-- because there is no need for a user to access it.

--

function Ref (Pointer : My_String_Ptr) return My_Safe_String;



end Test_My_String.Handle;

This package defines the type My_Safe_String which can be used with less care about memory allocation and deallocation. A handle can be copied using the standard assignment. A new string object can be created from a string. The value it points to can be accessed using the function Value, etc. It is a good practice to provide Create returning a handle instead of a direct use of Ref on an existing object, because it prevents referring stack-allocated objects which could get out of scope before handles to them. Object.Finalize would notice that and raise Program_Error. An implementation of My_Safe_String might look like follows.

package body Test_My_String.Handle is



function Create (Value : String) return My_Safe_String is

Ptr : My_String_Ptr := new My_String (Value'Length);

begin

Ptr.Value := Value;

return Ref (Ptr);

end Create;



function Value (Reference : My_Safe_String) return String is

begin

return Ptr (Reference).Value;

end Value;



procedure Copy

( Reference : in out My_Safe_String;

New_Value : String

) is

begin

Reference := Create (New_Value);

end Copy;



procedure Copy

( Reference : in out My_Safe_String;

New_Value : My_Safe_String

) is

begin

Reference := Create (Value (New_Value));

end Copy;



function Ref (Pointer : My_String_Ptr) return My_Safe_String is

begin

return (My_String_Handle.Ref (Pointer) with null record );

end Ref;



end Test_My_String.Handle;

1.4. Bounded arrays of objects

The package Object.Handle.Generic_Bounded_Array defines the type Bounded_Array. An instance of Bounded_Array is a fixed size array of references to objects. It is same as an array of handles to objects but more efficient.

generic

type Index_Type is (<>);

type Handle_Type is new Handle with private ;

package Object.Handle.Generic_Bounded_Array is ...

Here Index_Type is the type used to index the array elements. Handle_Type is any descendant of Handle including itself. The type Bounded_Array is defined in the package as:

type Bounded_Array (First, Last : Index_Type) is

new Ada.Finalization.Controlled with private ;

The discriminants First and Last define the index range. The following operations are defined on Bounded_Array:

procedure Adjust (Container : in out Bounded_Array);

The assignment makes a copy of the array.

function Append

( Container : Bounded_Array;

Element : Object_Type_Ptr := null ;

Count : Natural := 1

) return Bounded_Array;

function Append

( Container : Bounded_Array;

Element : Handle_Type;

Count : Natural := 1

) return Bounded_Array;

These functions add Element Count times to the end of Container. The result will have the lower bound Container.First. Constraint_Error is propagated when the upper bound cannot be represented in Index_Type.

function Delete

( Container : Bounded_Array;

From : Index_Type;

Count : Natural := 1

) return Bounded_Array;

This function deletes Count elements from Container starting with the element From. When Count exceeds the number of elements in the array, the available elements are removed. The lower bound of the result is Container.First, except the case when all elements are removed. For an empty result, the lower bound is Index_Type'Succ (Index_Type'First). Constraint_Error is propagated when the result should be empty, but Index_Type has less than two values. It is also propagated when From is not in Container.

procedure Finalize (Container : in out Bounded_Array);

The destructor may delete some objects referenced by the array.

procedure Fill

( Container : in out Bounded_Array;

From : Index_Type;

To : Index_Type;

Element : Object_Type_Ptr := null

);

procedure Fill

( Container : in out Bounded_Array;

From : Index_Type;

To : Index_Type;

Element : Handle_Type

);

These procedures are used to put in / replace a range of array elements. The range From..To is filled with Element. Nothing happens if From > To. Otherwise Constraint_Error is propagated when From..To is not in Container.First..Constainer.Last.

function Get

( Container : Bounded_Array;

Index : Index_Type

) return Object_Type_Ptr;

This function returns either a pointer to an object or null .

function Get

( Container : Bounded_Array;

From : Index_Type;

To : Index_Type

) return Bounded_Array;

This function returns a slice of Container. The lower index of the slice is From, the upper index is To. Constraint_Error is propagated when From..To is not empty and does not belong to the range First..Last of Container.

function Prepend

( Container : Bounded_Array;

Element : Object_Type_Ptr := null ;

Count : Natural := 1

) return Bounded_Array;

function Prepend

( Container : Bounded_Array;

Element : Handle_Type;

Count : Natural := 1

) return Bounded_Array;

These functions add Element Count times in front of Container. The result will have the upper bound Container.Last. Constraint_Error is propagated when the upper bound cannot be represented in Index_Type.

procedure Put

( Container : in out Bounded_Array;

Index : Index_Type;

Element : Object_Type_Ptr

);

procedure Put

( Container : in out Bounded_Array;

Index : Index_Type;

Element : Handle_Type

);

These procedures are used to put in / replace an array element using its index. Constraint_Error is propagated when Index is illegal.

procedure Put

( Container : in out Bounded_Array;

From : Index_Type;

To : Index_Type;

Elements : Bounded_Array

);

This procedures replaces the slice From..To of Container with Elements. Container and Elements can be the same object. Else if Elements is shorter than the slice, the rightmost elements of the slice are replaced with invalid handles. When Elements is longer, then its rightmost elements are ignored. The operation is void when From..To is empty. Constraint_Error is propagated when From..To is not empty and does not belong to the range First..Last of Container.

function Ref

( Container : Bounded_Array;

Index : Index_Type

) return Handle_Type;

function " & " (Left, Right : Bounded_Array) return Bounded_Array;

This function returns a concatenation of two arrays. If Right is empty, the result Left, else if Left is empty, the result is Right. Otherwise, the lower bound of the result is Index_Type'First.

Empty : constant Bounded_Array;

Empty array constant.

1.5. Unbounded arrays of objects

The package Object.Handle.Generic_Unbounded_Array defines the type Unbounded_Array. An instance of Unbounded_Array is an unbounded array of references to objects. The package has same functionality as an instance of Generic_Unbounded_Array with Handle as Object_Type, but it is more efficient.

generic

type Index_Type is (<>);

type Handle_Type is new Handle with private ;

Minimal_Size : Positive := 64 ;

Increment : Natural := 50 ;

package Object.Handle.Generic_Unbounded_Array is ...

Here:

Index_Type is the type used to index the array elements;

Handle_Type is the type of handles to the elements. It is any descendant of Handle or the Handle itself;

Minimal_Size is the minimal number of elements by which the array vector is enlarged. When the first element is put into the array, this will be the initial vector size.

Increment controls further vector enlargements. The vector is enlarged by n*Increment/100, where n is the current vector size. If the evaluated increment of the vector size is less than Minimal_Size elements, then the latter is used instead.

The type is declared as:

type Unbounded_Array is new Ada.Finalization.Controlled with private ;

The following operations are defined on Unbounded_Array:

procedure Adjust (Container : in out Unbounded_Array);

procedure Erase (Container : in out Unbounded_Array);

This procedure removes all elements from Container making it empty. The objects referenced only by Container will be deleted.

procedure Finalize (Container : in out Unbounded_Array);

The destructor may delete some objects referenced by the array.

function First

( Container : Unbounded_Array;

) return Index_Type;

This function returns the current lower bound of the array. Constraint_Error is propagated when the array is empty.

function Get

( Container : Unbounded_Array;

Index : Index_Type

) return Object_Type_Ptr;

This function returns either a pointer to an object or null .

function Last

( Container : Unbounded_Array;

) return Index_Type;

This function returns the current upper bound of the array. Constraint_Error is propagated when the array is empty.

procedure Put

( Container : in out Unbounded_Array;

Index : Index_Type;

Element : Object_Type_Ptr

);

procedure Put

( Container : in out Unbounded_Array;

Index : Index_Type;

Element : Handle_Type

);

These procedures are used to put in / replace an array element using its index. The array is automatically expanded as necessary. It never happens if Element is null or an invalid handle.

function Ref

( Container : Unbounded_Array;

Index : Index_Type

) return Handle_Type;

1.6. Unbounded sets of objects

The package Object.Handle.Generic_Set defines the type Set. An instance of Generic_Set is a set of references to objects. The package has same functionality as an instance of Generic_Set with Handle as Object_Type, but it is more efficient. It has the following generic parameters:

generic

Minimal_Size : Positive := 64 ;

Increment : Natural := 50 ;

package Object.Handle.Generic_Set is ...

Here:

Minimal_Size is the minimal number of elements by which set is enlarged. When the first element is put into the array, this will be the initial vector size.

Increment controls further vector enlargements. The vector is enlarged by n*Increment/100, where n is the current vector size. If the evaluated increment of the vector size is less than Minimal_Size elements, then the latter is used instead.

The type Set is declared as:

type Set is new Ada.Finalization.Controlled with private ;

The following operations are defined on Set:

procedure Add (Container : in out Set; Item : Handle);

procedure Add (Container : in out Set; Item : Object_Type_Ptr);

procedure Add (Container : in out Set; Items : Set);

These procedures are used to add an object to a Set or all items of one set to another. The parameter Item can be either a handle or a pointer to the object. Nothing happens if an item is already in the set or pointer is an invalid handle or null .

procedure Adjust (Container : in out Set);

The assignment does not make a copy of the Container. It just increments an internal use count of the set body. A set will be physically copied only when a destructive operation is applied to it.

function Create return Set;

This function returns an empty set.

procedure Erase (Container : in out Set);

This procedure removes all objects from the set. The objects referenced only by Container will be deleted.

procedure Finalize (Container : in out Set);

The destructor may delete some objects referenced by Container.

function Find (Container : Set; Item : Handle)

return Integer;

function Find (Container : Set; Item : Object_Type'Class)

return Integer;

function Find (Container : Set; Item : Object_Type_Ptr)

return Integer;

This function is used to Item in the set Container. The result is either a positive index of the found item or a negated index of the place where the item should be if it were in the set.

function Get (Container : Set; Index : Positive)

return Object_Type_Ptr;

This function is used to get an item of the set Container using a positive index. The result is a pointer to the object. It is valid as long as the object is in the set. See also Ref which represents a safer way of accessing the set items. Constraint_Error is propagated if Index is wrong.

function Get_Size (Container : Set) return Natural;

This function returns the number of items in the set.

function Is_Empty (Container : Set) return Boolean;

True is returned if Container is empty.

function Is_In (Container : Set; Item : Handle)

return Boolean;

function Is_In (Container : Set; Item : Object_Type'Class)

return Boolean;

function Is_In (Container : Set; Item : Object_Type_Ptr)

return Boolean;

True is returned if Item is in Container. Item can be either a pointer to the object, a handle to it or the object itself. The result is always false when Item is invalid or null .

function Ref (Container : Set; Index : Positive) return Handle;

This function is used to get an item of the set Container using a positive index. The result is a handle to the object. Constraint_Error is propagated if Index is wrong.

procedure Remove (Container : in out Set; Index : Positive);

procedure Remove (Container : in out Set; Item : Handle);

procedure Remove (Container : in out Set; Item : Object_Type'Class);

procedure Remove (Container : in out Set; Item : Object_Type_Ptr);

procedure Remove (Container : in out Set; Items : Set);

These procedures are used to remove items from the set Container. An item can be removed either by its index, or explicitly by a pointer, object or handle to it, or else by specifying a set of items to be removed. If a particular item is not in the set, then nothing happens. Also nothing happens if a handle is illegal or pointer is null . Constraint_Error is propagated when item index is wrong.

function " and " (Left, Right : Set) return Set;

function " or " (Left, Right : Set) return Set;

function " xor " (Left, Right : Set) return Set;

These functions are conventional set operations - intersection, union, difference. Difference is defined as a set which items are only in one of the sets Left and Right.

function " = " (Left, Right : Set) return Boolean;

True is returned if both sets contain same items.

1.7. Universal sets of objects

The packages Object.Handle.Generic_Handle_Set resembles Object.Handle.Generic_Set, but it is more universal. It allows to specify a user-defined types both for the object handles and for the weak references to objects (usually pointers). It has the following generic parameters:

generic

type Handle_Type is new Handle with private ;

type Object_Ptr_Type is private ;

Null_Object_Ptr : Object_Ptr_Type ;

with function Ptr (Object : Handle_Type) return Object_Ptr_Type is <>;

with function Ref (Object : Object_Ptr_Type) return Handle_Type is <>;

with function To_Object_Ptr (Object : Object_Ptr_Type) return Object_Type_Ptr is <>;

with function " < " (Left, Right : Object_Ptr_Type) return Boolean is <>;

with function " = " (Left, Right : Object_Ptr_Type) return Boolean is <>;

Minimal_Size : Positive := 64 ;

Increment : Natural := 50 ;

package Object.Handle.Generic_Handle_Set is ...

Here:

Handle_Type is a type derived from Handle to be used as an object handle (strong reference);

Object_Ptr_Type is a type used to reference objects (weak reference). Usually it is a record type containing a pointer to Object_Type'Class;

Null_Object_Ptr is a value never be put in the set. Usually it has the object pointer set to null ;

; Ptr is a function that converts a handle to the object to a reference. It is analogous to the function Ptr of the parent package;

Ref is a function that converts a reference to the object to a handle to. It is similar to the function Ref of the parent package;

To_Object_Ptr is a function that gets Object_Type_Ptr from an object reference;

" < " and " = " are comparisons defined on object references;

" and " " are comparisons defined on object references; Minimal_Size is the minimal number of elements by which set is enlarged. When the first element is put into the array, this will be the initial vector size.

Increment controls further vector enlargements. The vector is enlarged by n*Increment/100, where n is the current vector size. If the evaluated increment of the vector size is less than Minimal_Size elements, then the latter is used instead.

In other aspects both packages are identical. The interface subprograms described below are similar in both. The Handle should be read Handle_Type when Object.Handle.Generic_Handle_Set is considered.

The type Set is declared as:

type Set is new Ada.Finalization.Controlled with private ;

The following operations are defined on Set:

procedure Add (Container : in out Set; Item : Handle_Type);

procedure Add (Container : in out Set; Item : Object_Ptr_Type);

procedure Add (Container : in out Set; Items : Set);

These procedures are used to add an object to a set or all items of one set to another. The parameter Item can be either a handle or a pointer to the object. Nothing happens if an item is already in the set or pointer is an invalid handle or null .

procedure Adjust (Container : in out Set);

The assignment does not make a copy of the Container. It just increments an internal use count of the set body. A set will be physicaly copied only when a destructive operation is applied to it.

function Create return Set;

This function returns an empty set.

procedure Erase (Container : in out Set);

This procedure removes all objects from the set. The objects referenced only by Container will be deleted.

procedure Finalize (Container : in out Set);

The destructor may delete some objects referenced by Container.

function Find (Container : Set; Item : Handle_Type)

return Integer;

function Find (Container : Set; Item : Object_Ptr_Type)

return Integer;

This function is used to Item in the set Container. The result is either a positive index of the found item or a negated index of the place where the item should be if it were in the set.

function Get (Container : Set; Index : Positive)

return Object_Ptr_Type;

This function is used to get an item of the set Container using a positive index. The result is a pointer to the object. It is valid as long as the object is in the set. See also Ref which represents a safer way of accessing the set items. Constraint_Error is propagated if Index is wrong.

function Get_Size (Container : Set) return Natural;

This function returns the number of items in the set.

function Is_Empty (Container : Set) return Boolean;

True is returned if Container is empty.

function Is_In (Container : Set; Item : Handle_Type)

return Boolean;

function Is_In (Container : Set; Item : Object_Ptr_Type)

return Boolean;

True is returned if Item is in Container. Item can be either a pointer to an object or a handle to it. The result is always false when Item is invalid or null .

function Ref (Container : Set; Index : Positive) return Handle_Type;

This function is used to get an item of the set Container using a positive index. The result is a handle to the object. Constraint_Error is propagated if Index is wrong.

procedure Remove (Container : in out Set; Index : Positive);

procedure Remove (Container : in out Set; Item : Handle_Type);

procedure Remove (Container : in out Set; Item : Object_Ptr_Type);

procedure Remove (Container : in out Set; Items : Set);

These procedures are used to remove items from the set Container. An item can be removed either by its index, or explicitly by a pointer or handle to it, or else by specifying a set of items to be removed. If a particular item is not in the set, then nothing happens. Also nothing happens if a handle is illegal or pointer is null . Constraint_Error is propagated when item index is wrong.

function " and " (Left, Right : Set) return Set;

function " or " (Left, Right : Set) return Set;

function " xor " (Left, Right : Set) return Set;

These functions are conventional set operations - intersection, union, difference. Difference is defined as a set which items are only in one of the sets Left and Right.

function " = " (Left, Right : Set) return Boolean;

The function returns true is returned if both sets contain same items.

2. Persistency

2.1. Persistent objects

A persistent object is one stored in an external storage independent on the application that originally created it. A persistent object can be restored from the external storage in a fully functional state in the same or other application. The provided implementation of persistent objects was designed with the following goals in mind:

Portability across different platforms in the sense that an object can be stored on one platform and successfully restored on another;

Independency on the nature of the external storage;

Support for dependent objects;

Garbage collection within the application;

A possibility to implement garbage collection within the external storage;

Automatic synchronization with the persistent storage upon finalization.

Like other objects, persistent ones are normally accessed through handles.

2.1.1. Types

The package Object.Archived defines the type Deposit serving as the abstract base type for all persistent objects:

type Deposit is abstract new Entity with private ;

type Deposit_Ptr is access Deposit'Class;

A type derived from d from Deposit should:

Override the procedure Store which will be used to get a string description of the object;

Define a procedure with the profile of Restore which will create the object from its string description;

Register the restore procedure as a new class. This could be done once during elaboration of the package deriving the type from Deposit

Override Get_Class to return the object class for which Restore was registered;

Optionally, override Get_Referents if the object may depend on other objects which thus have to be stored/restored together with it:

If it overrides Finalize, then it shall call Close from there before the object becomes impossible to store.

Objects may depend on other objects, but these dependencies may not be circular. Store and Restore provide forth and back string conversions. String was chosen instead of Stream_Element_Array to make it portable across different systems.

Storing an object :

Get_Referents is called. Each object it refers is archived first. The order of the objects in the list is important and has to be preserved; Get_Class is called and its result is archived; Store is called and its result is finally archived.

Restoring an object :

A list of objects the archived object depends on is built, the objects are restored as necessary; The object's class string is obtained; Restore is finally called with these parameters. The class is used to select an appropriate Restore . This is equivalent to dispatching according to the class. The list of available classes and their Restore procedures is formed by calls to Register

The type Backward_Link is used when it is necessary to monitor deletion of an object.

type Backward_Link is abstract new Entity with private ;

type Backward_Link_Ptr is access Backward_Link'Class;

Reference counting is used to prevent deletion of a Deposit object, when it is in use. Such objects are referenced through handles. These are direct links to the object, also known as strong references. But sometimes it is necessary to break the dependency of one object from another to delete the latter. For this the former object may get a notification about a desire to delete a referent. Upon this notification it can invalidate the handle to the referent and so allow the collector to delete it. A notification object is derived from Backward_Link, which represent a backward link from a referred object to a dependent one. Each Deposit object maintains a list of its backward links, also called weak references. Typically an external storage connection object tracks all persistent objects which are in the memory at the moment. Usually it has an index of such memory resident objects. A record of this index has a handle to a specialized descendant of Backward_Link. So when an object is no more in use and so the last handle to it disappears, the object is automatically destroyed. In the course of this operation the storage connection object becomes a notification via call to Destroyed. At this point the object being destroyed can be stored and then removed from the external storage index of memory resident objects.

type Deposit_Container is abstract

new Ada.Finalization.Controlled with private ;

The type Deposit_Container is an abstract specialized container for Deposit objects. The container operates as a container of handles. That is when an object is put into it, then the object will not be deleted until it is in. Physically a reference to the object is placed into the container. Deposit_Container objects are used upon object storing and restoring to keep the list of things the object depends on. Deposit_Container is not limited so it can be copied when necessary. The child packages Object.Archived.Sets and Object.Archived.Lists provide unordered (set) and ordered (list) implementations of Deposit_Container.

2.1.2. Operations on objects

procedure Close (Object : in out Deposit'Class);

This class-wide procedure is called before finalization of a persistent object. It cleans the list of backward links. So it plays the role of a class-wide destructor. Finalize should always call it. For example, if the derived type is a descendant of Deposit overriding Finalize, then the implementation should look like:

procedure Finalize (Object : in out Derived) is

begin

Close (Object);

... -- finalization of Derived

Finalize (Deposit (Object));

end Finalize;

It is safe to call it multiple times, though it is essential to call it before any vital object data get finalized. So Finalization of a type derived from Derived may call Close as well. Note that in Ada Finalize is called prior finalization of any object's components. So it is safe to use them. However, keep in mind that task components (if any) are though not yet finalized, but completed before Finalize, thus neither Store nor Get_Referents may communicate with task components of the object.

procedure Create

( Source : String;

Pointer : in out Integer;

Class : String;

List : Deposit_Container'Class;

Object : out Deposit_Ptr

);

This procedure calls Restore for Class simulating a dispatching call. Name_Error is propagated if Class is not a registered object class. The string Source contains object description to be restored starting from the character Source (Pointer). Pointer is advanced to the first object following from the used ones. The parameter Object accepts a pointer to the newly created object.

Exceptions Data_Error Syntax error End_Error Nothing matched Layout_Error The value of Pointer is not in the range Source'First..Source'Last+1 Name_Error Class is not a registered class Use_Error Insufficient dependencies list

procedure Delete (Object : in out Deposit'Class);

This procedure is used when Object is being deleted. On each item in the Object's obituary notices delivery list, Delete is called. This has the effect that some references to Object may disappear and so the object will be collected. Note that a call to Delete does not guaranty Object's deletion, because some references to it, may still be present. It is safe to add new backward links to the Object's notification list from Delete, because the items are appended at the end of the delivery list. This also means that they will receive a Deleted callback in the course of the same notification. It is also safe to remove backward links from the list. Though Object's deletion is not guaranteed it might happen. So to prevent undefined behavior a caller should hold a handle to Object when it calls to Delete.

procedure Finalize (Object : in out Deposit);

Upon finalization backward links list is cleaned. All interested parties receive a notification via call to Destroyed. A derived type implementation have to call Finalize as well as Close.

procedure Free (Object : in out Deposit_Ptr);

This procedure is used to delete manually created objects. It is never called for existing objects, only for improperly constructed ones from an implementation of Restore.

function Get_Class (Object : Deposit) return String is abstract;

This function returns the class of Object. The class is a string uniquely describing the object's type. It is analogous to external type tag representation. Though, different types of objects may share same class if necessary.

procedure Get_Referents

( Object : Deposit;

Container : in out Deposit_Container'Class

);

This procedure adds objects referenced from Object to Container objects. Only immediately viewed objects are stored there. No deep search has to be made to detect all objects. Objects shall not depend recursively. The default implementation does nothing, which behavior corresponds to an independent object. An implementation may raise Use_Error on a wrong object. See also notes about Close.

function Is_Modified (Object : Deposit)

return Boolean is abstract;

This function is used to check if Object's state was changed. Persistent objects serving as proxies to a persistent storage will require synchronization if this function returns true . An implementation of a mutable object would normally have a Boolean flag to be set by any destructive operation or new object creation.

procedure Reset_Modified (Object : in out Deposit) is abstract;

This procedure is used to reset Object's state modification flag. It is called immediately after synchronization the object with the persistent storage.

type Restore is access procedure

( Source : String;

Pointer : in out Integer;

Class : String;

List : Deposit_Container'Class;

Object : out Deposit_Ptr

);

This procedure creates a new object from its string representation. It parses Source starting from Source (Pointer). Pointer is then advanced to the first character following the object's description in the string. The procedure has to be dispatching depending on the object's class, which is impossible in Ada. For this reason it is defined as an access to procedure type. Each object class has to define such a function and register it (see Register_Class). The parameter Class contains the actual object class according to which dispatch to an implementation of Restore was made. The parameter List contains the references to the objects the restored object depends on. The order of the objects in the list is same as one returned in Get_Referents. The result is a newly allocated object pointed by the Object parameter. An implementation may raise the following exceptions to indicate errors:

Exceptions Data_Error Syntax error End_Error Nothing matched Layout_Error The value of Pointer is not in the range Source'First..Source'Last+1 Use_Error Insufficient dependencies list

procedure Store

( Destination : in out String;

Pointer : in out Integer;

Object : Deposit

) is abstract ;

An implementation places string describing Object is into Destination starting from the position specified by Pointer. Pointer is then advanced to the next position following the output. Layout_Error is propagated when Pointer not in Source'First..Source'Last + 1 or there is no room for output. Use_Error can be raised when Object is wrong. See also notes about Close.

2.1.3. Operations on backward links to objects

procedure Attach

( Link : Backward_Link_Ptr;

Object : Deposit_Ptr

);

This procedure places Link at the end of Object's delivery list. If it is already in another list then it is removed from there first. Nothing happens if Object is null .

procedure Deleted

( Link : in out Backward_Link;

Temps : in out Deposit_Container'Class

) is abstract ;

This procedure is used when an object is requested to be deleted. Normally Deleted is called as a result of object deletion request via call to Delete. The parameter Temps is the list of temporal objects the implementation might create. For example, some objects might be created to be notified within the course of the operation performed by the caller. Note that the caller should hold a handle to Link, to allow the callee to undertake actions which would otherwise lead to Link deletion. Note also that object's finalization does not cause a call to Delete it calls Destroyed instead.

procedure Destroyed (Link : in out Backward_Link) is abstract ;

This procedure is used when an object is finally destroyed, but is still fully operable. Thus an implementation of Destroyed may safely access the object referred by Link. It may for example synchronize the object with the external storage or remove the object from the index cache etc. The caller should hold a handle to Link.

procedure Detach (Link : in out Backward_Link);

This procedure removes Link from object's delivery list, if any.

procedure Finalize (Link : in out Backward_Link);

This procedure should be called by a derived type if overridden. Link is removed for object's delivery list if any.

function Self (Link : Backward_Link) return Backward_Link_Ptr;

This function returns a pointer to the link object (to Link itself). Constraint_Error is propagated when Link is not bound to any object.

function This (Link : Backward_Linkont color="#0000FF">return Deposit_Ptr;

This function returns a pointer to the target of Link. Constraint_Error is propagated when Link is not bound to any object.

The package Backward_Link_Handles provides handles to Backward_Link objects.

The child package Backward_Link_Handles.Sets provides sets of handles to Backward_Link object.

2.1.4. Operations on containers

procedure Add

( Container : in out Deposit_Container;

Object : Deposit_Ptr;

Backward : Boolean := False

) is abstract ;

This procedure puts a reference to Object into Container. The implementation should ensure that Object will not be destroyed until it is in. The parameter Backward, when true indicates a backward link. Backward links are used when the dependent object associated with the container can survive deletion of Object. It is an optional parameter which may be ignored by some implementations. When it is supported, then marking an Object as a backward link should override the effect of any placing the same object as a direct link (with Backward = false ). Nothing happens if Object is null .

procedure Erase (Container : in out Deposit_Container) is abstract ;

This procedure removes all objects from Container.

function Get

( Container : Deposit_Container;

Index : Positive

) return Deposit_Ptr is abstract ;

This function is used to enumerate the objects in a container Objects indices start with 1. Contraint_Error is propagated when Index is wrong.

function Get_Size (Container : Deposit_Container)

return Natural is abstract ;

This function returns the number of objects in Container, i.e. the largest possible index allowed in Get. 0 is returned when the container is empty. Note that the objects in a container need not to be all different. This depends on the container implementation.

function Is_Backward

( Container : Deposit_Container;

Object : Deposit_Ptr

) return Boolean is abstract ;

The result of this function is true if a backward link is used for Object in Container. Constraint_Error is propagated when Object is not in Container. Use_Error is propagated when the container implementation does not distinguish direct and backward links.

function Is_Empty (Container : Deposit_Container'Class)

return Boolean;

This function returns true if Container is empty. It is class-wide.

function Is_In

( Container : Deposit_Container;

Object : Deposit_Ptr

) return Boolean is abstract ;

This function returns true if Object is in Container. Note that null cannot be in any container.

2.1.5. Registering classes of objects

function Is_Registered (Class : String) return Boolean;

This function returns true if there is a class of objects registered under the name Class.

procedure Register_Class

( Class : String;

Constructor : Restore

);

This procedure is used to register each new class of objects. It is analogous to creating a dispatching table. It is necessary to register a class to make Restore functions working. Nothing happens if the class is already registered and has same constructor. Name_Error is propagated when class is registered with a different constructor.

2.1.6. Sets of persistent objects

The package Object.Archived.Sets provides an implementation of Deposit_Container. The type Deposit_Set is derived there:

type Deposit_Set is new Deposit_Container with private ;

Sets do not distinguish multiple insertion of an object. they also ignore the Backward parameter of Add. So Is_Backward will raise Use_Error. Additionally to the predefined operations, Deposit_Set provides standard set-operations:

procedure Remove

( Container : in out Deposit_Set;

Object : Deposit_Ptr

);

This procedure removes Object from Container. Nothing happens if it is null or not in.

function " and " (Left, Right : Deposit_Set) return Deposit_Set;

function " or " (Left, Right : Deposit_Set) return Deposit_Set;

function " xor " (Left, Right : Deposit_Set) return Deposit_Set;

These functions are conventional set operations - intersection, union, difference. Difference is defined as a set which items are only in one of the sets Left and Right.

function " = " (Left, Right : Deposit_Set) return Boolean;

true is returned if both sets contain same items.

2.1.7. Lists of persistent objects

The package Object.Archived.Lists provides an implementation of Deposit_Container. The type Deposit_List is derived there as:

type Deposit_List is new Deposit_Container with private ;

All objects in the list are enumerated from 1. The same object can occupy several places in the list. In the external storage Deposit_List can be stored as a set of objects, where objects do not repeat, followed by a list of values identifying the objects in the set. Additionally to the predefined operations, Deposit_List provides:

function Get_Total (Container : Deposit_List) return Natural;

This function returns the number of distinct objects in Container. This value is less or equal to the one returned by Get_Size.

function Is_First

( Container : Deposit_List;

Index : Positive

) return Boolean;

This function returns true if Index is the least index of the object it specifies. I.e. the least index of the object returned by Get (Container, Index). Constraint_Error is propagated if Index is wrong.

2.1.8. Referent objects enumeration

The package Object.Archived.Iterators provides an abstract iterator of references:

type References_Iterator

( Referents : access Deposit_Container'Class

) is new Ada.Finalization.Limited_Controlled with private;

The type References_Iterator can be used directly or be extended. It provides the following operations:

procedure Enumerate

( Iterator : in out References_Iterator'Class;

Object : Deposit'Class

);

This class-wide procedure is called to enumerate references of Object. This same procedure is used for both starting the process and continuing it for each found reference. Enumerate calls Get_Referents for Object and places all found objects which Object depends on into Iterator.Referents. all . A found object is placed only once which is detected by looking into Iterator.Referents. all . The object itself is not put there. After completion the caller may inspect Iterator.Referents. all for any found objects.

procedure On_Each

( Iterator : in out References_Iterator;

Referent : Deposit_Ptr

);

This procedure can be overridden. It is called by Enumerate each time a new object is found. It may raise an exception to stop the iteration process. This exception will then propagate out of Enumerate.

2.2. Handles to persistent objects

Persistent objects are subject of garbage collection. The recommended way to access them is through handles, which prevents premature destruction of objects in use. Handles can be aggregated into other objects to express object dependencies. Note that circular dependencies shall be avoided. The best way to do it is to design object in a way that would exclude any possibility of circular dependencies. If that is not possible, then Is_Dependent should be used to check dependencies at run time. The generic package Object.Archived.Handle defines the type Handle used to reference persistent object. It is derived from Handle obtained by an instantiation of Object.Handle:

generic

type Object_Type (<>) is abstract new Deposit with private ;

type Object_Ptr_Type is access Object_Type'Class;

package Handles is new Object.Handle (Deposit, Deposit_Ptr);

type Handle is new Handles.Handle with null record ;

The formal parameters of the package are:

Object_Type is a descendant of Deposit . It can be different from Deposit when it is necessary to narrow the class of persistent objects;

Object_Ptr_Type is a class-wide pointer to Object_Type. Note that Object_Ptr_Type should use the same storage pool as Deposit_Ptr, because the implementation requires conversions between them. Unfortunately there is no formal way to express this requirement in Ada. Hopefully the compiler would refuse infeasible Object.Archived.Handle instantiations.

There is a ready-to use instantiation of Object.Archived.Handle with Deposit and Deposit_Ptr as the actual parameters: Deposit_Handles.

The package Object.Archived.Handle defines the following operations on Handle:

procedure Add

( Container : in out Deposit_Container;

Object : Handle;

Backward : Boolean := False

) is abstract ;

This procedure puts Object into Container. The parameter Backward, when true indicates a backward link. Backward links are used when the dependent object associated with the container can survive deletion of Object. Constraint_Error is propagated when Object is an invalid handle.

procedure Delete (Object : in out Handle);

This procedure requests deletion of the object pointed by the handle Object. As the result of the operation Object becomes an invalid handle. The object itself is deleted if possible. Nothing happens if Object is not a valid handle.

function Get_Class (Object : Handle) return String;

This function returns the class of Object. The class is a string uniquely describing the object's type. It is analogous to an external type tag representation. Though, different types of objects may have same class if necessary.

procedure Get_References

( Object : Handle;

Container : in out Deposit_Container'Class

);

This procedure adds to Container references to all objects the object specified by the handle Object depends on. No objects added if Object is an invalid handle.

procedure Invalidate (Object : in out Handle);

This procedure detaches handle from the object (if any) it points to. The result handle cannot be used to access any object. The referenced object is destroyed if it was the last handle.

function Is_Backward

( Container : Deposit_Container'Class;

Object : Handle

) return Boolean;

This function returns true if a backward link used for Object in Container. Contstraint_Error is propagated when Object is not in Container or invalid handle. Use_Error does when Container does not distinguish direct and backward links.

function Is_Dependent

( Dependant : Handle;

Referent : Handle

) return Boolean;

function Is_Dependent

( Dependant : Handle;

Referents : Deposit_Container'Class

) return Boolean;

These functions check whether Dependant refers to Referent or, when the second parameter is a container, then whether Dependant refers to any of the objects from that container. The result is false if Dependant, Referent is invalid or Referents is empty.

function Is_In

( Container : Deposit_Container'Class;

Object : Handle

) return Boolean;

This function returns true if Object is in Container. When Object is an invalid handle, the result false .

function Is_Valid (Object : Handle) return Boolean;

This function checks whether a handle points to any object, i.e. is valid.

function Ptr (Object : Handle) return Deposit_Ptr;

This function is used to get a pointer to the object the handle Object points to. The pointer of to the object shall be used no longer the handle it was get from exists. A safe way to do it is to avoid declarations of any variables of the type Deposit_Ptr.

function Ref (Thing : Object_Type_Ptr) return Handle;

This function is used to get a handle from a pointer to an persistent object.

function Ref

( Container : Deposit_Container'Class;

Index : Positive

) return Handle;

This function can be used to enumerate the objects in a container. Objects are enumerated from 1. The result is a valid handle to an object in Container. Contraint_Error is propagated when Index is wrong. Note that objects may repeat in containers of some types.

function References (Object : Handle) return Deposit_Set;

This function is used to query all objects its argument depends on. The result is a set of objects. It is empty if Object is an invalid handle.

procedure Set (Object : in out Handle; Thing : Object_Type_Ptr);

This procedure resets the handle Object to a possibly another object. In the course of this operation the previously pointed object may be destroyed if Object was the last handle pointing to it. It is safe when Thing is the object already pointed by the handle. When Thing is null , this procedure is equivalent to Invalidate.

The package Deposit_Handles provides an instantiation of Object.Archived.Handle:

package Deposit_Handles is

new Object.Archived.Handle (Deposit, Deposit_Ptr);

2.3. Persistent directories

There is no need to have dedicated objects to serve as persistent directories as any object could become a directory. Nevertheless the package Persistent.Directory provides objects which can be used as directories. They have no any functionality except an ability to persist. The package declares:

procedure Create

( Storage : in out Storage_Handle;

Directory : out Deposit_Handle;

Name : String;

Parent : Deposit_Handle := Root_Directory

);

This procedure creates a new directory with the name Name and Parent as the parent directory. The result is a handle Directory to the object. The parameter Storage is a handle Storage_Handle to the persistent storage object where the directory has to be created.

Exceptions Constraint_Error Invalid handle Storage, Parent is not persistent in Storage Data_Error Inconsistent Storage Name_Error Illegal name (such as empty), name conflict

function Is_Directory (Object : Deposit_Handle) return Boolean;

This function returns true if Object is a valid handle to a directory object.

Directory_Class : constant String := " Directory ";

Is the class name of the directory objects.

2.4. Persistent storage implementation example

This paragraph describes a simplified example of persistent storage. It provides an implementation of a persistent storage based on direct access file. As an example of persistent objects serve nodes of binary trees.

2.4.1. Persistent storage implementation

The implementation uses a direct access file to store objects. Each object is stored in one file record. The record number serves as the object key. Observe that the implementation is independent from any implementation of concrete persistent object types (derived from Deposit). This example serves illustrative purpose. For abstract persistent storage interface see Persistent, Persistent.Handle. For persistent storage implementations see Persistent.Handle.Factory.

with Ada.Direct_IO;

with Ada.Finalization;

with Generic_Map;

with Object.Handle;



with Object.Archived; use Object.Archived;

with Deposit_Handles; use Deposit_Handles;



package Test_Persistent_File_Storage is

--

-- File_Storage -- Direct I/O based storage for persistent objects

--

type File_Storage is

new Ada.Finalization.Limited_Controlled with private ;

--

-- Key -- To reference stored objects = record number 1..

--

type Key is new Integer ;

subtype Deposit_Handle is Deposit_Handles.Handle;



procedure Initialize (Storage : in out File_Storage);

procedure Finalize (Storage : in out File_Storage);

procedure Clean_Up;

function Store

( Storage : access File_Storage;

Object : Deposit_Handle

) return Key;

function Restore

( Storage : access File_Storage;

ID : Key

) return Deposit_Handle;

Here we declare the type File_Storage as a limited controlled type. The procedures Initialize / Finalize are overridden to provide construction / destruction. Upon construction the file is opened. Upon destruction it is closed. The procedure Clean_Up is provided to delete the file. The function Store will be used to store an object. It returns the object key, which identifies the object there. The key has the type Key also declared in this package. It is the number of the record reserved for the object in the file. When the object is already persistent in the file, its key is returned, so it is safe to call Store multiple times. The function Restore is the operation opposite to Store. It takes the object key and returns a handle to the object. Restore is also safe to call multiple times. So when the object referenced by a key, is already memory resident, a handle to it is returned instead of creating a new memory resident copy. The type Handle from the package Deposit_Handles is used to reference persistent objects. Deposit_Handles.Handle is "renamed" to Deposit_Handle for convenience. The objects themselves are never referenced directly but through handles only.

private

--

-- Index_Record -- One per bound object

--

type Index_Record (Storage : access File_Storage) is

new Backward_Link with

record

ID : Key; -- Object identifier

end record ;

type Index_Record_Ptr is access all Index_Record'Class;

--

-- Implementation of Backward_Link's operation

--

procedure Deleted

( Link : in out Index_Record;

Temps : in out Deposit_Container'Class

);

procedure Destroyed (Link : in out Index_Record);

A File_Storage object encapsulates the file and an index of all memory resident objects from that file. The index consists of Index_Records. One record is allocated per memory resident object. Index_Record is derived from Backward_Link to monitor what happens with the object. It also contains the object's key in the file. Two operations of Backward_Link need to be implemented: Deleted and Destroyed. The implementation of Deleted is called upon a request of object deletion. It does nothing in our case. Destroyed is called when the object is about to be finalized. In our case we store that object into the file. A more advanced implementation would check if the object was modified. It could also check if the object was requested for deletion and is no more referenced from other objects, in which case it can be removed from the persistent storage as well. But that would be too complex for a small illustrative example.

--

-- Record_Handles -- Handles to index records

--

package Record_Handles is

new Object.Handle (Index_Record, Index_Record_Ptr);

use Record_Handles;

subtype Record_Handle is Record_Handles.Handle;

--

-- Map : object pointer -> record handle

--

function " < " (Left, Right : Deposit_Ptr) return Boolean;

package Object_Maps is

new Generic_Map

( Key_Type => Deposit_Ptr,

Object_Type => Record_Handle

);

use Object_Maps;

subtype Object_Map is Object_Maps.Map;

--

-- Map : object key -> record handle

--

package Key_Maps is

new Generic_Map

( Key_Type => Key,

Object_Type => Record_Handle

);

use Key_Maps;

subtype Key_Map is Key_Maps.Map;

To reference Index_Record we will use handles provided by Record_Handles, an instantiation of Object.Handle. A handle to Index_Record is "renamed" to Record_Handle. Then we declare two maps: one to map objects to index records, another to map keys to the records. For this the package Generic_Map is instantiated once as Object_Maps and once as Key_Maps. Both use Record_Handle to reference Index_Record. So when the index record is deleted it is enough to remove it from the both maps and the object Index_Record will be automatically collected. Note also that Object_Map uses Deposit_Ptr, a pointer to the persistent object rather than a handle to it. It is important to allow object deletion. Otherwise an object would be never deleted as long as Index_Record referring it exists, i.e. up to File_Storage finalization. It would a thinkable, but too crude implementation. Generic_Map requires map keys be comparable, so the implementation declares " < " on Deposit_Ptr.

--

-- File record

--

type Reference_List is array (Integer range 1 .. 256 ) of Key;

type File_Record is record

Length : Natural := 0 ;

Count : Natural := 0 ;

References : Reference_List;

Descriptor : String ( 1 .. 1024 );

end record ;

package Record_Files is new Ada.Direct_IO (File_Record);

use Record_Files;

--

-- File_Storage -- Implementation

--

type File_Storage is

new Ada.Finalization.Limited_Controlled with

record

File : File_Type;

Object_To_Record : Object_Map;

Key_To_Record : Key_Map;

Last_ID : Key := 0 ; -- Last used object key

end record ;



end Test_Persistent_File_Storage;

The type File_Record describes one record in the file. The field References is the list of the keys of all the objects referred by the object. Count is the length of the list. The field Descriptor is a string describing the object. The length of the string is the field Length.

with Object.Archived.Lists; use Object.Archived.Lists;

with Strings_Edit; use Strings_Edit;



package body Test_Persistent_File_Storage is



function " < " (Left, Right : Deposit_Ptr) return Boolean is

begin

if Right = null then

return False;

elsif Left = null then

return True;

else

return Less (Left. all , Right. all );

end if ;

end " < ";



procedure Clean_Up is

File : File_Type;

begin

Create (File, Out_File, " test.dat ");

Close (File);

end Clean_Up;

The implementation of " < " uses Less defined on objects to order them. Clean_Up opens the file in Out_File mode and immediately closes it. This erases the file.

procedure Write

( Storage : in out File_Storage;

Object : Deposit'Class;

ID : Key

) is

References : Deposit_List;

Data_Record : File_Record;

Pointer : Integer := Data_Record.Descriptor'First;

begin

Get_Referents (Object, References);

Data_Record.Count := Get_Size (References);

for Item in 1 ..Data_Record.Count loop

Data_Record.References (Item) :=

Store (Storage' Access , Ref (References, Item));

end loop ;

Put (Data_Record.Descriptor, Pointer, Get_Class (Object));

Put (Data_Record.Descriptor, Pointer, " : ");

Store (Data_Record.Descriptor, Pointer, Object);

Data_Record.Length := Pointer;

Write (Storage.File, Data_Record, Count (ID));

end Write;

The procedure Write is defined to store an object under the specified key. It calls to Get_Referents to obtain the list of the objects the stored object needs. Then for each such object it calls Store to ensure the object persistency in the file. The keys returned by Store are placed into the References array. After that Write starts to form the field Description. It places the object class there (Get_Class) followed by a colon. Then object's Store is called to query the object description and to add it to Description. The completed object record is then written into the file.

procedure Initialize (Storage : in out File_Storage) is

begin

Open (Storage.File, Inout_File, " test.dat ");

Storage.Last_ID := Key (Size (Storage.File));

end Initialize;



procedure Finalize (Storage : in out File_Storage) is

begin

while not Is_Empty (Storage.Key_To_Record) loop

declare

Index_Item : Index_Record renames

Ptr (Get (Storage.Key_To_Record, Integer'( 1 ))). all ;

begin

Write (Storage, This (Index_Item). all , Index_Item.ID);

end ;

Remove (Storage.Key_To_Record, Integer'( 1 ));

Remove (Storage.Object_To_Record, 1 );

end loop ;

Close (Storage.File);

end Finalize;



procedure Bind

( Storage : access File_Storage;

Object : Deposit_Handle;

ID : Key

) is

Link_Ptr : Backward_Link_Ptr := new Index_Record (Storage);

Index_Item : Index_Record renames Index_Record (Link_Ptr. all );

begin

Index_Item.ID := ID;

Attach (Link_Ptr, Ptr (Object));

Add

( Storage.Object_To_Record,

Ptr (Object),

Ref (Index_Item'Unchecked_Access)

);

Add

( Storage.Key_To_Record,

ID,

Ref (Index_Item'Unchecked_Access)

);

end Bind;

The implementation of Initialize just opens the file for input / output and initializes the field Last_ID. Finalize goes through the index of memory resident objects (the key to object map). For each record of the index it calls Write to store the corresponding object and then removes the references to the index record from both maps. This in turn deletes the record itself. Note how This is used to get the object. The procedure Bind is defined to create an index record. It calls to Attach to bind Index_Record with the object and places handles to Index_Record in each of the maps. Ref is used to obtain them

function Store

( Storage : access File_Storage;

Object : Deposit_Handle

) return Key is

This : Deposit_Ptr := Ptr (Object);

begin

if This = null or else not Is_In (Storage.Object_To_Record, This)

then

Storage.Last_ID := Storage.Last_ID + 1 ;

Bind (Storage, Object, Storage.Last_ID);

return Storage.Last_ID;

else

return Ptr (Get (Storage.Object_To_Record, This)).ID;

end if ;

end Store;

The implementation of Store first looks into the index to check if it is already there. If yes it returns the key of the object. Otherwise it generates a new key by incrementing the field Last_ID and calls Bind to create a new index record.

function Restore (Storage : access File_Storage; ID : Key)

return Deposit_Handle i s

begin

if Is_In (Storage.Key_To_Record, ID) then

return Ref (This (Ptr (Get (Storage.Key_To_Record, ID)). all ));

else

--

-- Read the object from the file

--

declare

Data : File_Record;

List : Deposit_List;

Object : Deposit_Ptr;

Result : Deposit_Handle;

Pointer : Positive;

begin

Read (Storage.File, Data, Count (ID));

for No in 1 ..Data.Count loop

Add (List, Restore (Storage, Data.References (No)));

end loop ;

Pointer := Data.Descriptor'First;

while Data.Descriptor (Pointer) /= ' : ' loop

Pointer := Pointer + 1 ;

end loop ;

Pointer := Pointer + 1 ;

Create

( Data.Descriptor,

Pointer,

Data.Descriptor (Data.Descriptor'First..Pointer - 2 ),

List,

Object

);

Result := Ref (Object);

Bind (Storage, Result, ID);

return Result;

end ;

end if ;

end Restore;

The procedure Restore checks the index if an object with the specified key was already created. If yes it returns a handle to the object. This is used to get an object pointer from Index_Record. When the key identifies an unknown object, Restore reads its record from the file. The key is the record number. Restore goes through the array References and for each key calls itself to ensure this object to be restored too. The returned handle to that object is placed in a Deposit_List container. The container together with Descriptor's prefix (up to the first colon) as object's class name and the rest of it as the object's description, are passed to Create. That creates the object. A handle to it is then returned after Bind is called to place the object into the storage index.

procedure Deleted

( Link : in out Index_Record;

Temps : in out Deposit_Container'Class

) is

begin

null ;

end Deleted;



procedure Destroyed (Link : in out Index_Record) is

begin

Write (Link.Storage.all, This (Link). all , Link.ID);

Remove (Link.Storage.Object_To_Record, This (Link));

Remove (Link.Storage.Key_To_Record, Link.ID);

end Destroyed;



end Test_Persistent_File_Storage;

The implementation of Deleted does nothing. Destroyed writes the object into the file and then removes it from the index.

2.4.2. Persistent objects implementation

Let's take binary tree node as an example of persistent object. A node may have up to two successors or none. Predecessor - successor relation is naturally mapped to dependant - referent.

with Object.Archived; use Object.Archived;

with Deposit_Handles; use Deposit_Handles;



package Test_Persistent_Tree is

--

-- Nothing -- No node handle

--

function Nothing return Handle;

--

-- Create_Node -- This function creates a new node

--

-- Field - Identifies the node

-- Left - Successor on the left (a handle to)

-- Right - Successor on the right (a handle to)

--

function Create_Node

( Field : Integer;

Left : Handle := Nothing;

Right : Handle := Nothing

) return Handle;

--

-- Print -- Prints the tree rooted in a node

--

-- Root - The root node (a handle to)

--

procedure Print (Root : Handle; Indentation : String := "");



private

--

-- Node -- Binary tree node type

--

type Node is new Deposit with record

Field : Integer; -- Node identifier

Left : Handle; -- Left successor, a handle to

Right : Handle; -- Right successor, a handle to

end record ;

--

-- Implementation of Deposit's operations

--

function Get_Class (Object : Node) return String;

procedure Get_Referents

( Object : Node;

Container : in out Deposit_Container'Class

);

function Is_Modified (Object : Node) return Boolean;

procedure Reset_Modified (Object : in out Node);

procedure Restore

( Source : String;

Pointer : in out Integer;

Class : String;

List : Deposit_Container'Class;

Object : out Deposit_Ptr

);

procedure Store

( Destination : in out String;

Pointer : in out Integer;

Object : Node

);

end Test_Persistent_Tree;

The public part of the package declares the function Create_Node and the procedure Print. Create_Node creates a new node and returns a handle to it. All nodes are referenced using Handle of Deposit_Handles. Each node is identified by an integer number. The next two parameters of Create_Node are the handles to the left and right successors. They are defaulted to an invalid handle for which the function Nothing is also declared. It plays role of a constant invalid handle. The procedure Print is used for control. It prints the tree rooted in the node specified by the parameter Root.

The private part is straightforward. It declares the type Node as a descendant of Deposit. The operations Get_Class, Get_Referents, Is_Modified, Reset_Modified, Restore and Store are overridden to provide implementations.

with Ada.Text_IO; use Ada.Text_IO;

with Strings_Edit; use Strings_Edit;

with Strings_Edit.Integers; use Strings_Edit.Integers;



package body Test_Persistent_Tree is

Class : constant String := " Node "; -- The class of



function Nothing return Handle is

None : Handle;

begin

return None;

end Nothing;



function Create_Node

( Field : Integer;

Left : Handle := Nothing;

Right : Handle := Nothing

) return Handle is

Node_Ptr : Deposit_Ptr := new Node;

Object : Node renames Node (Node_Ptr. all );

begin

Object.Field := Field;

Object.Left := Left;

Object.Right := Right;

return Ref (Node_Ptr);

end Create_Node;



function Get_Class (Object : Node) return String is

begin

return Class;

end Get_Class;



procedure Get_Referents

( Object : Node;

Container : in out Deposit_Container'Class

) is

begin

if Is_Valid (Object.Left) then

Add (Container, Object.Left);

end if ;

if Is_Valid (Object.Right) then

Add (Container, Object.Right);

end if ;

end Get_Referents;



function Is_Modified (Object : Node) return Boolean i s

begin

return True; -- Save it always, do not care about performance

end Is_Modified;



procedure Reset_Modified (Object : in out Node) is

begin

null ;

end Reset_Modified;

The implementation of Get_Referents places handles to the node successors into a Deposit_Container. The left successor is placed first. Is_Modified and Reset_Modified are void for sake of simplicity. So a node is always written into the persistent storage even if it is not changed.

procedure Restore

( Source : String;

Pointer : in out Integer;

Class : String;

List : Deposit_Container'Class;

Object : out Deposit_Ptr

) is

Field : Integer;

Left : Handle;

Right : Handle;

begin

if Source (Pointer) = ' < ' then

Left := Ref (List, 1 );

if Source (Pointer + 1 ) = ' > ' then

Right := Ref (List, 2 );

end if ;

elsif Source (Pointer + 1 ) = ' > ' then

Right := Ref (List, 1 );

end if ;

Pointer := Pointer + 2 ;

Get (Source, Pointer, Field);

Object := new Node;

declare

Item : Node renames Node (Object. all );

begin

Item.Field := Field;

Item.Left := Left;

Item.Right := Right;

end ;

exception

when others =>

raise Data_Error;

end Restore;

The implementation of Restore first gets description of node dependencies from the source string. It is two characters. The first one is either '<' if there is a left successor or '-' otherwise. The second is '>' if there is a right successor or else '-'. After that it gets the node identifier (plain integer number). Then a new node object is allocated. Note that the target access type should be Deposit_Ptr to ensure right storage pool selection.

procedure Store

( Destination : in out String;

Pointer : in out Integer;

Object : Node

) is

begin

if Is_Valid (Object.Left) then

Put (Destination, Pointer, " < ");

else

Put (Destination, Pointer, " - ");

end if ;

if Is_Valid (Object.Right) then

Put (Destination, Pointer, " > ");

else

Put (Destination, Pointer, " - ");

end if ;

Put (Destination, Pointer, Object.Field);

end Store;



procedure Print (Root : Handle; Indentation : String := "") is

begin

if Is_Valid (Root) then

declare

The_Node : Node renames Node (Ptr (Root). all );

begin

Put_Line (Indentation & " \_ " & Image (The_Node.Field));

Print (The_Node.Left, Indentation & " | ");

Print (The_Node.Right, Indentation & " ");

end ;

else

Put_Line (Indentation & " \_* ");

end if ;

end Print;



begin

Register_Class (Class, Restore' Access );

end Test_Persistent_Tree;

The procedure Store is reverse to Restore. Also the package defines a new class of persistent objects named Node. For this it calls Register_Class once upon elaboration with the class name and a pointer to Restore as parameters.

2.4.3. Test program

The test program is shown below. It consists of two sessions. In the first session an object is stored. In the second one it is restored.

with Ada.Text_IO; use Ada.Text_IO;

with Test_Persistent_File_Storage; use Test_Persistent_File_Storage;

with Test_Persistent_Tree; use Test_Persistent_Tree;

with Deposit_Handles; use Deposit_Handles;



procedure Test_Persistent_Storage is

Root_Key : Key;

begin

Clean_Up;

Put_Line (" Session 1 ");

declare

DB : aliased File_Storage;

Root : Handle;

begin

Root :=

Create_Node

( 1 ,

Create_Node ( 2 ),

Create_Node

( 3 ,

Create_Node

( 4 ,

Create_Node ( 5 )

),

Create_Node ( 6 )

) );

Print (Root);

Root_Key := Store (DB' Access , Root);

end ;

Put_Line (" Session 2 ");

declare

DB : aliased File_Storage;

Root : Handle;

begin

Root := Restore (DB' Access , Root_Key);

Print (Root);

end ;

end Test_Persistent_Storage;

The test program first calls Clean_Up to delete any existing storage file. Then it declares DB, a File_Storage object. After that a tree is created and Root becomes a handle to the tree root node. The tree is printed and then its root node is stored into DB. There result of the operation is the external key of the root node. This key can be used to restore the object. Note that the whole tree is stored because the any node depends on its child nodes. What Store does depends on the implementation. In our case physical file writing happens either upon finalization of the storage object (DB) or upon finalization of the persistent object (Root). Both objects are go out of scope at end closing the first session. The second session uses Restore and the external key to bring the root node back from the storage. Again, all the objects it depends on are restored as well. Finally, the restored tree is printed.

2.4.4. Predefined persistent storage test

The test program that uses an ODBC data base as a persistent storage is shown below:

with Ada.Text_IO; use Ada.Text_IO;

with Deposit_Handles; use Deposit_Handles;

with Persistent.Handle; use Persistent.Handle;

with Test_Persistent_Tree; use Test_Persistent_Tree;

with Test_ODBC_Session; use Test_ODBC_Session;



procedure Test_ODBC_Persistence is

Name : constant String := " The tree ";

begin

Put_Line (" Session 1 ");

declare

DB : Storage_Handle := Open;

Root : Handle;

begin

Root :=

Create_Node

( 1 ,

Create_Node ( 2 ),

Create_Node

( 3 ,

Create_Node

( 4 ,

Create_Node ( 5 )

),

Create_Node ( 6 )

) );

Print (Root);

Put (DB, Root, Name);

end ;

Put_Line (" Session 2 ");

declare

DB : Storage_Handle := Open;

Root : Handle;

begin

Root := Get (DB, Name);

Print (Root);

end ;

end Test_APQ_Persistence;

Then it declares DB, a Storage_Handle. The handle is initialized using the function Open defined in Test_ODBC_Session.adb. It prompts for connection parameters and then calls Persistent.ODBC.Create. After that a tree is created and Root becomes a handle to the tree root node. The tree is printed and then its root node is stored into DB as "The three". For this it calls Put. Note that the whole tree is stored because the any node depends on its child nodes. The second session uses Get and the name "The three" to bring the root node back from the storage. Again, all the objects it depends on are restored as well. Finally, the restored tree is printed. Carefully observe that the package Test_Persistent_Tree needed no modifications to be able to work with a different type of storage.

This test program modified for APQ and SQLite are in the files test_APQ_persistence.adb and test_APQ_persistence.adb correspondingly.

2.5. Abstract persistent storage

The package Persistent provides an abstract persistent storage communication object. The corresponding persistent storage can be implemented on the basis of a plain file, data base etc. Objects in the storage are identified by their names. Additionally anonymous objects can be created and deleted as required by the named ones. If an object depends on some other objects, then when stored into the storage, the referred objects are stored as well. If they do not already persist there, these objects will be anonymous. Anonymous persistent objects are subject of garbage collection. The way of collection is determined by the implementation.

The objects can be named. The object names are UTF-8 encoded strings. An implementation can internally provide other encoding when the persistent storage natively supports Unicode different to UTF-8. Named objects are deleted only on explicit request or when they loose names becoming anonymous. Named objects build a hierarchy, where one named object can be a descendant of another. This hierarchy is a forest. The parent objects serve as folders for their children. It is not specified which nature parent objects should have. Objects of any kind can serve as parents. Also the parent-child relation does not impose any additional dependency between the objects. It is a relation solely between the names of.

The procedure Delete can be applied to a handle to the object in order to request its deletion. If the object cannot be deleted immediately it becomes anonymous for later collection. Persistent storage interfaces are itself objects and are a subject of garbage collection as well. When a named parent object becomes anonymous all its descendants do as well.

The package defines the abstract type Storage_Object which describes the interface of a persistent storage communication object. It is derived from Entity, so persistent storage interface objects are subject of garbage collection:

type Storage_Object is abstract new Object.Entity with private ;

type Storage_Object_Ptr is access Storage_Object'Class;

for Storage_Object_Ptr'Storage_Pool

use Object.Entity_Ptr'Storage_Pool;

It is strongly recommended not to directly use derivatives of Storage_Object. For this purpose serve handles to the objects.

The subtype Deposit_Handle is provided for convenience in referring persistent objects. It "renames" the handle type of the package Deposit_Handles:

subtype Deposit_Handle is Deposit_Handles.Handle;

The root-level objects have no parent. When a subprogram requires a parent specification the constant Root_Directory is used:

The package instantiates Generic_Set to obtain sets of object names.

package Catalogue is

new Generic_Set

( Object_Type => Unbounded_String,

Null_Element => Null_Unbounded_String

);

The following operations are defined on Storage_Object:

function Get

( Storage : access Storage_Object;

Name : String;

Parent : Deposit_Handle := Root_Directory

) return Deposit_Handle is abstract ;

This function returns a handle to a persistent object by its name and a handle to the parent object. The root-level objects have no parents, in which case Parent is an invalid handle. An implementation should first check if the the persistent object already has a memory-resident counterpart. Otherwise it should create one from the persistent storage.

Exceptions Constraint_Error The object specified by Parent is not persistent in Storage Data_Error Inconsistent Storage End_Error No such object Use_Error The class of the object is unknown. This error means that there is no known Ada type yet registered to handle the objects from the persistent storage. Normally Ada types register their classes upon corresponding package elaboration. If the package is not used by the application, its persistent objects cannot be restored.

function Get_Class

( Storage : access Storage_Object;

Name : String;

Parent : Deposit_Handle := Root_Directory

) return String is abstract ;

This function returns the class of a persistent object by its name and a handle to the parent object.

Exceptions Constraint_Error The object specified by Parent is not persistent in Storage Data_Error Inconsistent Storage End_Error No such object

function Get_Creation_Time

( Storage : access Storage_Object;

Name : String;

Parent : Deposit_Handle := Root_Directory

) return Time is abstract ;

This function returns the creation time of a persistent object by its name and a handle to the parent object.

Exceptions Constraint_Error The object specified by Parent is not persistent in Storage Data_Error Inconsistent Storage End_Error No such object

function Get_List

( Storage : access Storage_Object;

Prefix : String := "";

Suffix : String := "";

Equivalence : Unicode_Mapping_Function := null ;

Parent : Deposit_Handle := Root_Directory

) return Catalogue.Set is abstract ;

This function returns a complete list of all named objects persistent in Storage which have parent object specified by the parameter Parent. The list does not include anonymous persistent objects, which have neither parents nor names. Only names starting with Prefix and ending with Suffix are returned. When names are compared two characters are considered same if their corresponding values returned by Equivalence are same. When Equivalence is null, it is assumed an identity mapping. For case insensitive mappings see Strings_Edit.UTF8.Mapping.To_Lowercase. Prefix and Suffix may not overlap when matched. The list is a set of object names.

Exceptions Constraint_Error The object specified by Parent is not persistent in Storage Data_Error Inconsistent Storage

function Get_Name

( Storage : access Storage_Object;

Object : Deposit_Handle

) return String is abstract ;

This function returns the object's name in Storage. The object is specified by its handle. Note that object names are relative to their parents, so only a pair name - parent does identify the object.

Exceptions Constraint_Error Invalid handle or Object does not persist in Storage Data_Error Inconsistent Storage Name_Error Object is anonymous

function Get_Parent

( Storage : access Storage_Object;

Object : Deposit_Handle

) return Deposit_Handle is abstract ;

This function returns the object's parent in Storage. The object is specified by its handle.

Exceptions Constraint_Error Invalid handle or Object does not persist in Storage Data_Error Inconsistent Storage Name_Error Object is anonymous

function Is_Descendant

( Storage : access Storage_Object;

Object : Deposit_Handle;

Parent : Deposit_Handle

) return Boolean is abstract ;

This function checks if Object is a direct or indirect descendant of Parent. The result is false if Object is invalid, or else specifies an anonymous or non-persisting in Storage object. Otherwise the result is true when Parent is invalid (i.e. identifies root-level objects) and false when Parent does not persist in Storage. Data_Error is propagated on error in Storage.

function Is_In

( Storage : access Storage_Object;

Name : String;

Parent : Deposit_Handle := Root_Directory

) return Boolean is abstract ;

function Is_In

( Storage : access Storage_Object;

Object : Deposit_Handle

) return Boolean is abstract ;

These functions check whether an object persists in Storage. The object can be identified either by its name and parent or by a handle to it. When Object is not a valid handle the result is false .

Exceptions Constraint_Error The object specified by Parent is not persistent in Storage Data_Error Inconsistent Storage

function Is_Named

( Storage : access Storage_Object;

Object : Deposit_Handle

) return Boolean is abstract ;

These functions check whether Object persists and named in Storage. When Object is not a valid handle the result is false .

Exceptions Data_Error Inconsistent Storage

procedure On_Error

( Storage : Storage_Object;

Text : String;

Error : Exception_Occurrence

);

This procedure is called on exceptions which cannot be handled, e.g. in Finalize. The default implementation does nothing. It can be overridden in order to write a trace log.

procedure Put

( Storage : in out Storage_Object;

Object : in out Deposit_Handle;

Name : String;

Parent : Deposit_Handle := Root_Directory

) is abstract ;

procedure Put

( Storage : in out Storage_Object;

Object : in out Deposit_Handle

) is abstract ;

These procedures are used to store Object in Storage. The parameters Name and Parent specify the object's name and parent in Storage. When omitted the object is stored as anonymous. Anonymous persistent objects are collected when not used, but not before their memory-resident counterpart vanishes. When Object already persists in Storage and Name and Parent are specified, then they are checked to be same. If this check fails, or Name is empty or illegal, or else conflicts with the name of another object Name_Error is propagated. When name is not specified, no check is made.

Exceptions Constraint_Error Invalid handle, Parent does not persist in Storage Data_Error Inconsistent Storage Name_Error Illegal name (such as empty) or name conflict

procedure Rename

( Storage : in out Storage_Object;

Old_Name : String;

Old_Parent : Deposit_Handle := Root_Directory

New_Name : String;

New_Parent : Deposit_Handle := Root_Directory

) is abstract ;

procedure Rename

( Storage : in out Storage_Object;

Object : in out Deposit_Handle;

New_Name : String;

New_Parent : Deposit_Handle := Root_Directory

) is abstract ;

These procedures change the name of the object specified by either its old name and parent (the parameters Old_Name, Old_Parent) or by a handle to it (the parameter Object). When renamed object was anonymous before renaming it becomes a named one. When Object is an invalid handle or does not refer to a persistent object then Constraint_Error is propagated. End_Error is propagated when Old_Name does not refer any persistent object. No object can become a parent of itself, so a check shall be made whether New_Parent specifies the object or any of its descendant. If yes, Name_Error is propagated.

Exceptions Constraint_Error Object is invalid handle or does not refer to any object in Storage. New_Parent does not persist in Storage. Data_Error Inconsistent Storage End_Error Old_Name indicates no object Name_Error Illegal name (such as empty) or name conflict. The object is an ancestor of its new parent.

procedure Unname

( Storage : in out Storage_Object;

Name : String;

Parent : Deposit_Handle := Root_Directory

) is abstract ;

procedure Unname

( Storage : in out Storage_Object;

Object : in out Deposit_Handle

) is abstract ;

These procedures make object anonymous. The object can be specified either by its name and parent or by a handle to it. Unnamed objects are automatically deleted when no more in use. Nothing happens if the object is already unnamed. Nothing also happens if Object is an invalid handle, not a handle to a persistent object or does not exist. Note that anonymous objects are not deleted as long as they have memory-resident counterparts. Observe the difference between Unname and Delete (Object.Archived.Delete) called on an object handle. Delete requests object deletion from both memory and persistent storage. Unname does it for persistent storage only. Both may have no immediate effect if the object is still in use. Note that when a parent object becomes anonymous so all its descendants do.

Exceptions Constraint_Error The object specified by Parent is not persistent in Storage Data_Error Inconsistent Storage

2.6. Handles to persistent storage

A persistent storage interface is itself an object, which can be referenced by another object. Usually it is a persistent object which memory-resident counterpart of is a proxy to the data in the persistent storage. For example, for a large data structure it might be very inefficient to load it all into the memory. In this case in the memory one would create a small proxy object, which will query the persistent storage for parts of the object's data as necessary. Such proxy object will require a reference to its persistent storage. This also would prevent the persistent storage interface object from premature destruction. This is why it is strongly recommended to use handles to persistent storage interface objects.

The package Persistent.Handle provides the type Storage_Handle, which serves as a handle to an abstract persistent storage interface object. It is guarantied that a persistent storage interface object will not be destroyed as long at least one handle refers to it.

type Storage_Handle is private ;

The following operations are defined on Storage_Handle:

function Get

( Storage : Storage_Handle;

Name : String / Wide_String;

Parent : Deposit_Handle := Root_Directory

) return Deposit_Handle;

This function searches for the specified object by its name and parent. The name is an UTF-8 encoded string or else a wide string. If the object is already available a handle to it is returned. Otherwise it first is restored from the persistent storage.

Exceptions Constraint_Error Invalid handle Storage, Parent is not persistent in Storage Data_Error Inconsistent Storage End_Error No such object Use_Error The class of the object is unknown. This error means that there is no known Ada type yet registered to handle the objects from the persistent storage. Normally Ada types register their classes upon corresponding package elaboration. If the package is not used by the application, its persistent objects cannot be restored.

function Get_Class

( Storage : access Storage_Handle;

Name : String / Wide_String;

Parent : Deposit_Handle := Root_Directory

) return String;

These functions return the class of a persistent object by its name and parent. The name can be specified either an UTF-8 encoded string or as a wide string.

Exceptions Constraint_Error Invalid handle Storage, Parent is not persistent in Storage Data_Error Inconsistent Storage End_Error No such object

function Get_Creation_Time

( Storage : access Storage_Handle;

Name : String / Wide_String;

Parent : Deposit_Handle := Root_Directory

) return Time;

These functions return the creation time of a persistent object by its name and parent. The name can be specified either an UTF-8 encoded string or as a wide string.

Exceptions Constraint_Error Invalid handle Storage, Parent is not persistent in Storage Data_Error Inconsistent Storage End_Error No such object

function Get_List

( Storage : Storage_Handle;

Prefix : String := "";

Suffix : String := "";

Equivalence : Unicode_Mapping_Function := null ;

Parent : Deposit_Handle := Root_Directory

) return Catalogue.Set;

function Get_List

( Storage : Storage_Handle;

Prefix : Wide_String;

Suffix : Wide_String;

Equivalence : Unicode_Mapping_Function := null ;

Parent : Deposit_Handle := Root_Directory

) return Catalogue.Set;

These functions return a list of all immediate children of Parent persistent in Storage. Only names starting with Prefix and ending with Suffix are eligible. When names are compared two characters are considered same if their corresponding values according to Equivalence are same. When Equivalence is null, it is assumed an identity mapping. For case insensitive mappings see Strings_Edit.UTF8.Mapping.To_Lowercase. Observe that Prefix may not overlap Suffix when matched. So if Prefix="AB" and Suffix="BC", then "ABC" does not fit, but "ABBC" does. The result of the function is a set of object names. Prefix and Suffix are either UTF-8 encoded or wide strings.

Exceptions Constraint_Error Invalid handle Storage, Parent is not persistent in Storage Data_Error Inconsistent Storage

function Get_Name

( Storage : Storage_Handle;

Object : Deposit_Handle

) return String;

This function returns the object's name in Storage. The object is specified by its handle. The result is an UTF-8 encoded string. Note that the object names are relative to the object's parent.

Exceptions Constraint_Error Invalid handle or Object does not persists in Storage Data_Error Inconsistent Storage Name_Error Object is anonymous

function Get_Parent

( Storage : Storage_Handle;

Object : Deposit_Handle

) return Deposit_Handle;

This function returns the object's parent in Storage. The object is specified by its handle.

Exceptions Constraint_Error Invalid handle or Object does not persists in Storage Data_Error Inconsistent Storage Name_Er