ObjectStore C++ Advanced API User Guide

Chapter 8

Dump/Load Facility

The ObjectStore dump/load facility allows you to

The dumped ASCII has a compact, human-readable format. It is editable with tools such as Perl, Awk, and Sed. You can use edited or unedited ASCII as input to the loader.

By default, objects are dumped in terms of the primitive values they directly or indirectly contain. You can use the default dump and load processes, or customize the dumping and loading of particular types of objects. You can, for example, dump and load objects in terms of sequences of high-level API operations needed to recreate them, rather than in terms of the primitive values they contain. This is appropriate for certain location-dependent structures, such as hash tables.

To enhance efficiency during a dump, database traversal is performed in address order whenever possible. To enhance efficiency during loads, loaders are generated by the dumper and tailored to the schema involved. This allows the elimination of most run-time schema lookups during the loading.

Read about the dump/load facility in ObjectStore Management Chapter 4, Utilities, osdump: Dumping Databases before you read the discussion in this chapter. This chapter focuses on When Is Customization Required? and addresses the following topics:

When Is Customization Required?

In most cases, customization is unnecessary. The basic types, pointers, ObjectStore references, collections, indexes, and most instances of classes are handled without any customization.

You might want to use customization to:

There are also some circumstances when you must take advantage of specialization. For example, if you have a database with objects whose structure depends on the locations of other objects, you might have to customize the dumping and loading of those objects.

A dumped object and its equivalent loaded object do not necessarily have the same location, that is, the same offsets in their segment. Among the implications of this are the following:

The default dumper and loader take into account the first implication, and the loader automatically adjusts all pointers in loaded databases to use the new locations.

The default dumper and loader also take into account the second implication for ObjectStore collections with hash-table representations. Since a dumped collection element hashes to a different value than the corresponding loaded element does, their hash-table slots are different. So the facility does not simply dump and load the array of slots based on fundamental values (which would result in using the same slot for the dumped and loaded objects).

Instead, it dumps the collection in terms of sequences of high-level API operations (that is, string representations of create() and insert() arguments) that the loader can use to recreate the collection with the appropriate membership.

The default dumper and loader do not take into account the second implication for non-ObjectStore classes. If you have collection classes that use hash-table representations, you must customize their dumping and loading. Any other location-dependent details of data structures (such as encoded offsets) should also be dealt with through customization.

Although the facility provides a great deal of flexibility, customization typically takes the form described for ObjectStore collections above.

Customizing Dumps

If you want to dump and load a database containing a location-dependent data structure, you should dump and load the data structure in terms of a sequence of operations that recreates the data structure. The dumper emits the arguments for each operation in the sequence, and the loader recreates the data structure by performing the operations using the arguments in the dumped ASCII.

Creation Stages

Typically, this sequence of operations can be divided into two stages, corresponding to two stages of loading a data structure:

Since some objects required for the fixup stage might not exist during the initialization stage, the loader must typically perform the initialization stage, load other objects, and then perform the fixup stage. This means the dumper must dump the arguments for the initialization stage, dump all other objects (except nonroot objects), and then dump the arguments for the fixup stage.

For example, when the root of a collection is loaded, the collection elements might not yet exist, so the loader usually creates an empty collection, loads the elements (as well as other objects), and then inserts the elements. And the dumper usually dumps the create() arguments, dumps the elements (and all other objects except nonroot objects), and then dumps the insert() arguments.

It is important to distinguish between nonroots of a data structure and objects that are not part of the data structure at all. For example, if a collection's elements are pointers, the pointer objects are nonroots of the collection's data structure, but the objects pointed to by the elements are not part of the datastructure at all.

Dumper Actions

To accommodate these stages, the dumper operates in three different modes, performing different kinds of actions in each mode:

The following pseudocode summarizes the flow of control among modes:

for each database, db, specified on the command line {

      for each segment, seg, in db {

            // plan mode
            for each top-level object, o, in seg 
                  Invoke the planner for o's type on o

            // dump mode
            for each top-level object, o, in seg 
                  Invoke the object dumper for o's type on o
                  If necessary, create a fixup dumper for o, and 
                  associate it with either seg, db, or the whole dump

            // fixup mode
            for each fixup dumper associated with seg
                  Invoke that fixup dumper
      }

      // fixup mode
      for each fixup dumper associated with db
            Invoke that fixup dumper
}

// fixup mode
for each fixup associated with the whole dump
      invoke that fixup dumper
By default, object-dump actions sometimes invoke other object-dump actions on embedded objects. The following summarizes the behavior of the different object dump actions invoked by the default dumper for different types of objects:

Default fixup-dump actions also invoke fixup-dump actions on embedded objects, in just the same way.

For each object form the loader processes, it invokes a type-specific object loader, in a manner similar to that described for the dumper.

Supplying Customized Type-Specific Actions

To customize the dumping of objects of a given type, you specialize base classes whose instances represent the three kinds of dumper actions:

In order to support planning for a given class, class, choose one of the following approaches:

The invocation operator of a planner corresponding to a given class takes an instance of the class as argument. For example, my_hash_table_planner::operator ()() takes an instance of my_hash_table as argument. With the deep approach, the invocation function typically navigates from the root object to the nonroots, and creates an ignore record for each nonroot object.

With the shallow approach, the invocation function creates an ignore record for the argument or does nothing, depending on whether the argument is a nonroot object of the data structure whose dump is being customized.

The shallow approach has better paging behavior, so use it if possible.

For each derived class supporting object dumping, planning, and fixup dumping, perform these steps:

In addition, for each derived class supporting object dumping and planning, perform these steps:

The following table shows what members of each class you should implement. Your implementations of these functions specify the objects to be ignored and the dump formats.
Base ClassMembers
os_Planning_actionoperator ()
os_Dumper_specializationoperator ()
should_use_default_constructor()
get_specialization_name()
os_Fixup_dumperConstructor
dump_info()
duplicate()

Customizing Loads

During a load, the loader processes object and fixup forms in the order in which they were emitted by the dumper. To provide a customized loader for a given type, you implement functions that support the following tasks:

To do this, derive a class from each of the following base classes:

For each derived class, you must perform these steps:

The following table shows what members of each class you should implement.
Base ClassMembers
os_Type_infodata
Constructor
os_Type_loaderoperator ()
load()
create()
fixup()
get()
os_Type_fixup_infofixup_data
Constructor
os_Type_fixup_loaderoperator ()
load()
fixup()
get()

Specializing os_Planning_action

Your specialization of os_Planning_action handles planning, including the identification of objects for which object forms should not be generated.

If you are using the shallow approach to planning, then, for each type, type, of nonroot object you must define a class that is

For example, if my_table_entry is a type of nonroot object, use the following:

class my_table_entry_planner : public os_Planning_action {...}
If you are using the deep approach to planning, then, for the type, type, of the root object, you must define a class that is

For example, if my_hash_table is a type whose dump and load you want to customize, use the following:

class my_table_entry_planner : public os_Planning_action {...}
The derived class must implement operator ()().

You must also define and register an instance of the derived class.

If a class does not require fixups, then it does not require planning. Even if it does require fixups, it might not require planning. If the fixups do not create any objects that would be dumped normally without planning, planning might be unnecessary.

Implementing operator ()()

void  type_planner::operator () (
      const os_type& actual_type,
      void* object
)
Using shallow approach
If you are taking the shallow approach to planning, implement type_planner::operator ()() (where type is one type of nonroot object) to do the following:

If the object should be ignored, do the following:

Here is a typical implementation:

void  type_planner::operator () (
      const os_type& actual_type,
      void* object
)
{
      ...

      /* Do type verification (optional) */
      assert_is_ type(actual_type, object);

      ...

       type& obj = ( type&)*object;
      ...

      if (should_ignore(obj) {
            Dumper_reference ignored_object(&obj);
            Database_table::get().insert(ignored_object);
      }
      ...
}
In this example, assert_is_ type() and should_ignore() are user-defined.

If necessary, you can include application-specific processing in place of the "..."s.

Using deep approach
If you are using the deep approach to planning, implement type_:oplanner::operator ()() (where type is the type of the root object) to do the following:

For each associated nonroot object, do the following:

Here is a typical implementation:

{
      ...
      /* Do type verification (optional) */
      assert_is_ type(actual_type, object);
      ...
       type& obj = ( type&)*object;
      ...
      if (should_ignore(obj) {
            Dumper_reference ignored_object(&obj);
            Database_table::get().insert(ignored_object);
      }

      (* reachable_type_planner)(obj.reachable_pointer);
      ...
}

If you can define dump-related members of type, you can use the following approach:

void < type>_planner::operator () (const os_type& actual_type,
                  void* object)
{
      ...
       type& obj = ( type&)*object;
      obj.plan_dump();
      ...
}

void  type::plan_dump ()
{
      ...

      if (should_ignore(obj) {
            Dumper_reference ignored_object(&obj);
            Database_table::get().insert(ignored_object);
      }
      ...

      /* consider directly reachable objects */
      reachable_pointer->plan_dump();
      ...
}

Defining and Registering the Instance

You must define an instance:

static  type_planner the_ type_planner;
and register it by including an entry in the global array entries[]:

static os_Planner_registration_entry planner_entries[] = {
      ...
      os_Planner_registration_entry(" type", &the_ type_planner),
      ...
}

static const unsigned number_planner_registration_entries
      = OS_NUMBER_REGISTRATIONS(
            planner_entries, 
            os_Planner_registration_entry
);

static os_Planner_registration_block block(
      planner_entries, 
      number_planner_registration_entries,
      __FILE__,
      __LINE__
);
This code should be at top level.

Customizing Formatting by Specializing os_Dumper_specialization

Your specialization of os_Dumper_specialization handles the dumping of object forms.

To customize the dump and load of instances of a type, type, define a class that is

For example, to customize the dump and load of instances of my_collection, use the following:

class my_collection_dumper : public os_Dumper_specialization {...}
The derived class must implement the following functions:

Under some circumstances, you must also implement type_dumper::get_specialization_name().

You must also define and register an instance of the derived class.

Implementing operator ()()

void  type_dumper::operator () (
      const os_class_type& actual_class, 
      void* object
      )
An object form has the following structure:

 id ( Type)  value 
When you supply an object-form dumper, you are responsible for generation of the value portion only. The functions that generate the rest of the object form are inherited from os_Dumper_specialization.

The value portion is generated by operator ()(). Implement operator ()() to generate ASCII from which a loader can reconstruct function arguments. The arguments should be those required for recreation of the root portion of the object being dumped.

Define operator ()() to do the following:

Inserting a fixup dumper causes the dumper to generate a fixup form for object after generating all the object forms. When a load processes this kind of fixup form for object, it adjusts all pointers and C++ references in object so that they refer to the appropriate, newly loaded referent.

If type is a nonarray type, the typical implementation has the following form:

void  type_dumper::operator () (
      const os_class_type& actual_class, 
      void* object
)
{
      ...
      // optional type verification
      // assert function defined by user
      assert_is_ type(actual_class, object);
      ...
      // cast the void*
       type& obj = (<Type&>)*object;
      ...

      // output the object form
      get_stream() << obj.get_representation() << ' '
            << obj.get_size() << ' ';
      ...

      // create a Fixup_dumper on the stack
       type_fixup_dumper fixup(
            get_stream(), 
            *this, 
            actual_class, 
            object
);

      // insert a fixup dumper for processing at the end of the dump
      Database_type::get().insert(fixup);
}
This example assumes that the output of type::get_representation() and type::get_size() provides sufficient information to create the root of object.

You can insert application-specific processing in place of the "..."s, but this is not required.

You do not usually have to customize array types, since you can customize the element type of an array type. The default array dumper will call the custom dumper on each array element.

If you do want to customize the array dumper, implement this overloading of the invocation operator:

void  type_dumper::operator () (
      const os_class_type& actual_class, 
      void* object, 
      unsigned number_elements
)
{
      ...
}

Implementing should_use_default_constructor()

os_boolean should_use_default_constructor(
      const os_class_type& class_type
) const;
When you customize the dump and load of a type, you supply code to construct instances of the type during a load - see Implementing create(). This code is used for all nonembedded instances of the type. For an instance of the type embedded in a noncustomized type, the loader calls the customized type's constructor automatically, using code generated by the dumper.

For a given customized type, type, you determine which of two constructors is called in the dumper-generated code:

type_data has a data member for each data member and base class of type. For pointer and (C++) reference members of type, the type_data member should be of type os_Fixup_reference. For embedded class members, the type should be embedded_class_data. All data members corresponding to a base class should have the base class as value type.

All other data members should have the same value type as the coreresponding member of type. All data members of type_data should have the same name as the coreresponding member or base class of type.

If you implement type_Loader::should_use_default_constructor() to return 0, the dumper-generated code calls the no-argument constructor. If you implement type_Loader::should_use_default_constructor() to return 1, the dumper-generated code calls the special loader constructor.

Implementing get_specialization_name()

char* get_specialization_name(
      const os_class_type& class_type
) const;
You must define this function only if there is a subtype of type such that both of the following hold:

In this case, define the function to return the character string " type" given an os_class_type& for each such subtype.

Deleting the returned string is the responsibility of the caller of this function.

Defining and Registering the Dumper Instance

You must define an instance of type_dumper:

static  type_dumper the_ type_dumper;
and register it by including an entry in the global array entries[]:

static os_Dumper_registration_entry entries[] = {
      ...
      os_Dumper_registration_entry(" type", &the_ type_dumper),
      ...
};

static const unsigned number_dumper_registration_entries
      = OS_NUMBER_REGISTRATIONS(
            entries, 
            os_Dumper_registration_entry
      );

static os_Dumper_registration_block block(
      entries, 
      number_dumper_registration_entries,
      __FILE__,
      __LINE__
);
This code should be at top level.

Specializing os_Fixup_dumper

The dumper invokes type-specific fixup-dump actions, that typically emit strings from which the loader can reconstruct arguments. Your specialization of os_Fixup_dumper handles the dumping of fixup forms.

To customize the dump and load of instances of a type, type, define a class that is

For example, to customize the dumping and loading of instances of my_collection, use the following:

class my_collection_fixup_dumper : 
      public os_Fixup_Dumper {...}
The derived class must implement the following functions:

Implementing dump_info()

void  type_fixup_dumper::dump_info()
A fixup form has the following structure:

fixup  id ( Type)  info
When you supply a fixup-form dumper, you are responsible for generation of the info portion only. The functions that generate the rest of the fixup form are inherited from os_Fixup_dumper.

The info portion is generated by dump_info(). Implement dump_info() to generate ASCII from which a loader can reconstruct function arguments. The arguments should be those required for recreation of the nonroot portion of the object being fixed up.

Where type is the type of object being fixed up, the function should

To dump arguments that are pointers or C++ references, use the class os_Dumper_reference:

A loader can reconstruct the dumper reference from the load stream with operator >>(), and get the location of the newly loaded referent with os_Dumper_reference::resolve() or os_Dumper_reference::resolve().

For example, a fixup form for a collection might include ASCII from which a dumper can reconstruct pointers to all the collection's elements:

void  type_fixup_dumper::dump_info() const
{
      ...
      const void* object = get_object_to_fix();
      assert_is_ type(get_type(), object);
      ...
       type& obj = ( type&)*object;
       ...
      os_Dumper_reference ref;
      for (unsigned count = 0; count < obj.get_size(); ++count) {
             element_type& element = obj[count];
            ref = element;
            get_stream() << ref << ' ';
      }
      // info terminator -- assumes no null elements
      ref = 0;
      get_stream() << ref << ' ';
      ...
}
In this example,

If necessary, you can include additional application-specific processing in place of the "..."s.

Implementing duplicate()

Fixup&  type_fixup_dumper::duplicate()
Implement duplicate() to allocate a copy of this type_fixup_dumper in the specified segment. The following example assumes type_fixup_dumper defines a copy constructor and a get_os_typespec() function.

Fixup&  type_fixup_dumper::duplicate (
      os_segment& segment
) const
{
      return *new(segment,  type_fixup_dumper::get_os_typespec())
             type_fixup_dumper(*this);
}
Be sure to include type_fixup_dumper in the application schema of the emitted loader.

Implementing the Constructor

 type_fixup_dumper (
      os_Dumper_stream&, 
      os_Dumper&, 
      const os_class_type&, 
      const os_Dumper_reference object_to_fix,
      unsigned number_elements = 0
);
Implement the constructor to pass the arguments to the base type constructor:

os_Fixup_dumper (
      os_Dumper_stream&, 
      os_Dumper&, 
      const os_class_type&, 
      const os_Dumper_reference object_to_fix,
      unsigned number_elements = 0
);

Specializing os_Type_info

class  type_info : public os_Type_info
A type_info holds information about the loading of the object form currently being processed. See os_Type_info. The derived type must define two members:

For each instance of type being loaded, type_loader::operator ()() makes an instance of type_data, as well as an instance of type_info that points to the type_data. It passes the type_data to load(), which sets the fields of the type_data based on the contents of the dump stream.

type_loader::operator ()() then passes the type_info to create(), which uses the information to create the postload object.

Your specialization can add any members you find convenient.

Implementing data

 type_data &data;
To implement this public data member, derive a type, type_data, from os_Type_data (this base class has no members). Define a data member of type_data for each portion of the object-form value emitted by type_dumper::operator ()(). These members will be set by load() based on information in the load stream, and used later by create() and fixup() to recreate the object being loaded.

type_data should have a data member for each data member and base class of type. For pointer and (C++) reference members of type, the type_data member should be of type os_Fixup_reference. For embedded class members, the type should be embedded_class_data. All data members corresponding to a base class should have the base class as value type.

All other data members should have the same value type as the coreresponding member of type. All data members of type_data should have the same name as the coreresponding member or base class of type.

Implementing the Constructor

 type_info (
      os_Type_loader cur_loader, 
      os_Loader_stream lstr, 
      os_Object_info& info, 
       type_data &data_arg
);
Implement this function to set type_info::data to data_arg. Pass the first three arguments to the following os_Type_info constructor:

os_Type_info (
      os_Type_loader& cur_loader, 
      os_Loader_stream& lstr, 
      os_Object_info& info
);
cur_loader is the loader for the object currently being loaded.

lstr is the dump stream from which the current object form is being processed.

info is the loader info for the object being loaded.

When you call type_info:: type_info() from within type_loader::operator ()(),

You can define this constructor to have additional arguments if necessary for your specialization.

Specializing os_Type_loader

Your specialization of os_Type_loader handles the loading of object forms.

To customize the dumping and loading of instances of a type, type, define a class that is

For example, to customize the dumping and loading of instances of my_collection, use the following:

class my_collection_loader : 
      public os_Type_loader {...}
The derived class must implement the following functions:

You must also define and register an instance of the derived class.

Implementing operator ()()

os_Loader_action*  type_loader::operator () (
      os_Loader_stream& stream, 
      os_Loader_info& previous_info
)
Implement the invocation operator to do the following:

Here is an example:

Loader_action*  type_loader::operator () (
      os_Loader_stream& stream, 
      os_Loader_info& previous_info
)
{
      os_Object_info& object_info = previous_info;
       type_data data;
       type_info info(*this, stream, object_info, data);
      load(stream, data);
      create(info);
      return 0;
}

Implementing load()

Loader_action*  type_loader::load (
      Loader_stream& stream, 
      Type_data& given_data
) 
This function is responsible for reading the value portion of the object form from the load stream, and setting the data members of given_data accordingly.

Once load() sets the data members of given_data, create() or fixup() uses given_data to guide recreation of the object being loaded.

The function should do the following:

Returns 0 for success.

Here is an example:

Loader_action*  type_loader::load (
      Loader_stream& stream, 
      Type_data& given_data
) 
{
       type_data& data = ( type_data&) given_data;
      ...
      /* Input each part of the dumped value. */
      stream >> data.representation;
      stream >> data.size;
      ...
      return 0;
}
If the current portion of the dumped value is an embedded object form for a class, embedded_class, retrieve that class's loader with embedded_class_loader::get(), and call embedded_class_loader::load(), passing stream and the embedded_class_data embedded in type_data:

      /* Load embedded class. */
       embedded_type_loader::get().load(
            stream, 
            data. member
      );

Implementing create()

void  type_loader::create (Loader_info& given_info) ;
This function is responsible for creating the persistent object corresponding to the object form being loaded. Arguments to persistent new can be retrieved from given_info. Arguments to the object constructor can be retrieved from the type_data associated with given_info.

The function should

If you call the no-argument constructor, you must do the following:

If create() rather than fixup() sets the data member values, and there are embedded instances of type, you must do the following:

Since create() is not called for embedded objects, either the special loader constructor must exist, or fixup() must set all the data members.

The function must also create a mapping record that records the predump and postload locations of the object being loaded:

Finally, the function must call type_loader::fixup(), passing as arguments the type_data and the location of the newly loaded object (that is, the pointer returned by new).

Here is an example:

void <Type>_loader::create (Loader_info& given_info) 
{
       type_info& info = ( type_info&) given_info;
       type* value;
      void* location = info.get_replacing_location();

      if (location)
            value = ::new(location)  type(info.data);

      else {

            value = new (
                        &info.get_replacing_segment(), 
                         type::get_os_typespec()
                  )  type(info.data);

            // Insert a mapping of the constructed object's original
            // location to its replacing location into the Database_table. 

            const os_type& value_type = info.get_type();
            Dumper_reference replacing_location(value);
            info.set_replacing_location(value);

            Database_table::get().insert(
                  info.get_original_location(), 
                  replacing_location, 
                  value_type
            );

      }

      fixup(info.data, value);
}

Implementing fixup()

void  type_loader::fixup (Type_data& given_data, void* object) 
The default loader automatically adjusts pointers and references in loaded objects. If you supply a type-specific loader, type_loader, you must explicitly direct the loader to make these adjustments for instances of type.

You do this by defining fixup() to insert fixup records into the database table. Perform one insert for each pointer, C++ reference, or ObjectStore reference contained directly within instances of type. For each one, do the following:

fixup() is also responsible for setting members of the newly loaded object, if create() uses the no-argument constructor to construct the object being loaded.

If create() does not use the no-argument constructor, and instances of type contain no pointers, no C++ references, and no ObjectStore references, you do not have to implement this function. You never have to implement a no-op fixup().

Here is a typical implementation:

void  type_loader::fixup (Type_data& given_data, void* object) 
{
       type_data& data = ( type_data&) given_data;
       type& obj =  type& *object;
      ...
      /* Fixup pointer. */
      Dumper_reference pointer_location(&obj.pointer);
      Dumper_reference original_referent_location(data.pointer);
      Database_table::get().insert
            (Reference_fixup_kind::pointer, pointer_location,
            original_referent_location);
      ...
}
If a portion of the dumped value is an embedded object form for a class, embedded_class, retrieve that class's loader with embedded_class_loader::get(), and call embedded_class_loader::fixup(), passing the embedded_class_data embedded in type_data and the corresponding object embedded in obj:

      /* Fixup embedded object. */
       embedded_type_loader::get().fixup(
            data. member, 
            &obj. member
      );

Implementing get()

Define a global variable whose value is an instance of type_loader. Define type_loader::get() to return this instance:

static  type_loader the_ type_loader;

Type_loader&  type_loader::get () 
{
      return the_ type_loader;
}

Defining and Registering the Instance

You must define an instance:

static  type_loder the_ type_loader;
and register it by including an entry in the global array entries[]:

static os_Loader_registration_entry entries[] = {
      ...
      os_Loader_registration_entry(" type", &the_ type_loader),
      ...
}

static const unsigned number_loader_registration_entries = 
      OS_NUMBER_REGISTRATIONS(
            entries, 
            os_Loader_registration_entry
);

static os_Loader_registration_block block(
      entries, 
      number_loader_registration_entries,
      __FILE__,
      __LINE__
);
This code should be at top level.

Specializing os_Type_fixup_info

class  type_fixup_info : public os_Type_fixup_info
A type_fixup_info holds information about the loading of the fixup form currently being processed. os_Type_fixup_info is derived from os_Type_info.

type_fixup_info must define two members:

For each instance of type being loaded, type_fixup_loader::operator ()() makes an instance of type_fixup_data, as well as an instance of type_fixup_info that points to the type_fixup_data. It passes the type_fixup_data to load(), which sets the fields of the type_fixup_data based on the contents of the dump stream.

type_loader::operator ()() then passes the type_fixup_info to fixup(), which uses the information to create the postload object.

Your specialization can add any members you find convenient.

Implementing fixup_data

 type_fixup_data &fixup_data;
To implement this public data member, derive a type, type_fixup_data, from os_Type_data. Define a data member of type_fixup_data for each portion of the object-form value emitted by type_fixup_dumper::dump_info(). These members will be set by load() based on information in the load stream, and used later by fixup() to recreate the object being loaded.

Implementing the Constructor

 type_fixup_info (
      os_Type_fixup_loader cur_fixup_loader, 
      os_Loader_stream lstr, 
      os_Object_info& info, 
       type_fixup_data &data_arg
);
Implement this function to set type_fixup_info::data to data_arg. Pass the first three arguments to the following os_Type_fixup_info constructor:

os_Type_fixup_info (
      os_Type_loader& cur_loader, 
      os_Loader_stream& lstr, 
      os_Fixup_info& info
);
cur_loader is the loader for the object currently being fixed up.

lstr is the dump stream from which the current fixup form is being processed.

info is the fixup loader info for the object being fixed up.

When you call type_fixup_info:: type_fixup_info() from within type_loader::operator ()()

You can define this constructor to have additional arguments if necessary for your specialization.

Specializing os_Type_fixup_loader

Your specialization of os_Type_fixup_loader handles the loading of fixup forms.

To customize the dump and load of instances a type, type, define a class that is:

For example, to customize the dump and load of instances of my_collection, use the following:

class my_collection_fixup_loader : 
      public os_Type_fixup_loader {...}
The derived class must implement the following functions:

You must also define and register an instance of the derived class.

Implementing operator ()()

os_Loader_action*  type_fixup_loader::operator () (
      os_Loader_stream& stream, 
      os_Loader_info& previous_info
)
Implement the invocation operator to do the following:

This assumes that one call to load() consumes the entire info portion of a fixup form. You can also implement load() to consume just a part of the info portion, and call load() and fixup() multiple times from operator ()(). The latter approach makes loaders more scalable for large info portions.

Here is an example:

os_Loader_action*  type_fixup_loader::operator () (
      os_Loader_stream& stream, 
      os_Loader_info& previous_info
)
{
      os_Fixup_info& fixup_info = previous_info;
       type_fixup_data data;
       type_fixup_info info(*this, stream, fixup_info, data);
      while( load(stream, data))
            fixup(info);
      return 0;
}
This assumes load() is implemented to return 0 when the entire info portion has been consumed.

If the info portion is made up of dumped char*s, allocate the type_Fixup_info inside the load loop:

os_Loader_action*  type_fixup_loader::operator () (
      os_Loader_stream& stream, 
      os_Loader_info& previous_info
)
{
      os_Fixup_info& fixup_info = previous_info;
       type_fixup_data data;
      while( load(stream, data))
             type_fixup_info info(*this, stream, fixup_info, data);
            fixup(info);
      return 0;
}

Implementing load()

Loader_action* load (
      Loader_stream& stream, 
      Type_data& given_data
);
This function is responsible for reading the info portion of the fixup form from the load stream, and setting the data members of given_data accordingly. If the invocation operator calls load() just once, load() must consume all of the info portion. If the invocation operator calls load() multiple times, each call to load() must consume just part of the info portion.

Once load() sets the data members of given_data, fixup() uses given_data to guide recreation of the nonroot portion of the object being fixed up.

The function should do the following:

If load() consumes the entire info portion, returns 0 for success.

If load() consumes just part of the info portion, you can return 0 when the entire info portion has been consumed, and return nonzero otherwise. You must cast the return value to os_Loader_action*.

Here is an example:

Loader_action*  type_fixup_loader::load (
      Loader_stream& stream, 
      Type_data& given_data
) 
{
       type_fixup_data& data = ( type_fixup_data&) given_data;
      ...
      /* Input one part of the dumped value. */
      os_Dumper_reference original_ref;
      stream >> original_ref;
      if (original_ref == 0) // info terminator
            return ( (os_Loader_action*) 0 );
      else {
            data.dumper_ref = 
                  os_database_table::get().find_reference(original_ref);
            return ( (os_Loader_action*) 1 ); 
      }
}
If a portion of the dumped info is an embedded fixup form for a class, embedded_class, load it as follows:

For example:

      /* Load embedded class. */
       embedded_type_fixup_loader::get().load(
            stream, 
            data. member_3
      );

Implementing fixup()

void fixup (os_Type_fixup_info& info);
This function performs fixup based on the information passed in. For example, if the object being fixed up is a collection, and each portion of the fixup form identifies a collection element, fixup() would insert an element in the collection.

The function should

Here is an example:

void  type_fixup_loader::fixup (Type_fixup_info& given_info) 
{
       type_fixup_info& info = ( type_fixup_info&) info;

      Dumper_reference original_location = 
            info.get_original_location();
      if ( ! original_location) {
            // Handle error, there should only be a fixup for an
            // existing object. 
      }

      Dumper_reference replacing_location = 
            os_Database_table::get().find_reference(original_location);
      if ( ! replacing_location) {
            // Handle error, there should only be a fixup for an
            // existing object.
      }

       type* object = replacing_location;
      ...
      /* Do whatever needs to be done to fix the designated object. */

       element_type *the_element = ( element_type*) (
                  info.fixup_data.dumper_ref.resolve()
            );
      object.insert(the_element)
      ...
}
Note that info.fixup_data_.dumper_ref is set by type_fixup_loader::load().

Implementing get()

static os_Type_fixup_loader& get (); 
Define a global variable whose value is an instance of type_loader. Define the static function type_loader::get() to return this instance:

static  type_fixup_loader the_ type_fixup_loader;

os_Type_fixup_loader&  type_fixup_loader::get () 
{
      return the_ type_fixup_loader;
}

Registering the Fixup Loader

Define an instance of the derived type:

static  type_fixup_loader the_ type_fixup_loader;
Register the instance of the derived class by including an entry in the global array fixup_entries[]:

static os_Fixup_registration_entry fixup_entries[] = {
      ...
      os_Fixup_registration_entry(" type", &the_ type_fixup_loader),
      ...
}

static const unsigned number_fixup_registration_entries =
      OS_NUMBER_REGISTRATIONS(
            fixup_entries, 
            os_Fixup_registration_entry
);

static os_Fixup_registration_block block(
      fixup_entries,
      number_fixup_registration_entries,
      __FILE__,
      __LINE__
);

os_Database_table

The database table records the mapping between predump objects and postload objects, and stores sets of various kinds of fixups, as well as the ignored set. You can obtain the one and only instance of this class with the static member get().

os_Database_table::get()

static os_Database_table& get ();
Returns a reference to the (one and only) database table.

os_Database_table::insert()

void insert (
      const os_Dumper_reference source,
      const os_Dumper_reference target,
      const os_type& referent_type
);
Inserts a mapping record that associates a predump object with a postload object. Normally you call this from type_loader::create().

source is a dumper reference to the predump object.

target is a dumper reference to the postload object.

referent_type refers to an os_type representing the type of the object.

void insert (
      os_Reference_fixup_kind::Kind kind, 
      const os_Dumper_reference reference,
      const os_Dumper_reference referent_original_location
);
Inserts a fixup record that instructs the loader to adjust the pointer, C++ reference, or ObjectStore reference referred to by reference. The loader makes the adjustment after loading all object forms and before loading any fixup forms. Normally you call this from type_loader::fixup().

kind is one of the following:

reference refers to the pointer or reference to be fixed up.

referent_original_location refers to the predump referent of the pointer or reference to be fixed up.

void insert (
      os_segment&, 
      const os_Fixup& fixup
);
Associates the specified fixup dumper with the specified segment. Each fixup dumper associated with a segment is invoked after all object forms for that segment have been dumped. See Dumper Actions. Normally you call this function from type_dumper::operator ()().

void insert (
      os_database&, 
      const os_Fixup& fixup
);
Associates the specified fixup dumper with the specified database. Each fixup dumper associated with a database is invoked after all object forms for that database have been dumped. See Dumper Actions. Normally you call this function from type_dumper::operator ()().

void insert (
      const os_Fixup& fixup
);
Associates the specified fixup dumper with the entire dump. Each fixup dumper associated with the entire dump is invoked after all object forms for the dump have been dumped. See Dumper Actions. Normally you call this function from type_dumper::operator ()().

void insert (
      const os_Dumper_reference ignored_object
);
Inserts the specified object into the ignored set. If the object is already in the ignored set, the insertion is silently ignored. Before dumping an object, the dumper checks to see if the object is in the ignored set. If it is, the dumper does not dump the object.

ignored_object is a dumper reference to the object that should not be dumped.

os_Database_table::find_reference()

os_Dumper_reference find_reference (
      const os_Dumper_reference given_reference
) const;
Finds the postload object corresponding to a given predump object. Both objects are specified with instances of os_Dumper_reference. given_reference refers to the predump object. The returned reference refers to the corresponding postload object.

If there is no object that corresponds to the referent of given_reference, a null reference is returned.

If given_reference is null, a null reference is returned.

See also os_Database_table::insert() (the first overloading, for mapping records).

os_Database_table::is_ignored()

os_boolean is_ignored (const os_Dumper_reference obj) const;
Returns nonzero if obj is in the ignored set; returns nonzero otherwise. See os_Database_Table::insert(const os_Dumper_Reference().

os_Dumper_reference

Instances of the class os_reference can be used as substitutes for pointers to predump or postload objects. Given a reference to a predump object, you can retrieve a reference to the corresponding postload object (using os_Database_table::find_reference()). Dumper references are required as arguments to certain functions your specializations call, such as os_Database_table::insert().

As with a pointer, once the object referred to by a dumper reference is deleted, use of the reference accesses arbitrary data and might cause a segmentation violation.

You can construct or set a reference with os_Dumper_reference::os_Dumper_reference() or os_Dumper_reference::operator =(). You can resolve a reference with os_Dumper_reference::resolve() or os_Dumper_reference::resolve().

os_Dumper_reference::operator void*()

operator void*() const;
Returns the pointer for which the specified reference is a substitute.

os_Dumper_reference::operator =()

os_Dumper_reference& operator = (const os_Dumper_reference&);
Establishes the referent of the right operand as the referent of the left operand.

os_Dumper_reference& operator = (void* object);
Establishes the object pointed to by the right operand as the referent of the left operand.

os_Dumper_reference::os_Dumper_reference()

os_Dumper_reference (const void*);
Constructs a reference to substitute for the specified void*.

os_Dumper_reference (
      os_unsigned_int32 database_number,
      os_unsigned_int32 segment_number,
      os_unsigned_int32 offset
);
Constructs a reference to the object with the specified database number, segment number, and offset.

os_Dumper_reference (const os_Dumper_reference&);
Constructs a reference with the same referent as the specified reference.

os_Dumper_reference ();
Constructs a null reference, that is, a reference without a current referent. See os_Dumper_reference::operator =().

os_Dumper_reference::resolve()

void* resolve() const;
Returns the valid void* for which the specified reference is a substitute.

os_Dumper_reference::operator ==()

os_boolean operator == (const os_Dumper_reference&) const;
Returns 1 if the arguments have the same referent; returns 0 otherwise.

os_Dumper_reference::operator <()

os_boolean operator < (const os_Dumper_reference&) const;
If the first argument and second argument refer to elements of the same array or one beyond the end of the array, a return value of 1 indicates that the referent of the first argument precedes the referent of the second, and a return value of 0 indicates that it does not. Otherwise the results are undefined.

os_Dumper_reference::operator >()

os_boolean operator > (const os_Dumper_reference&) const;
If the first argument and second argument refer to elements of the same array or one beyond the end of the array, a return value of 1 indicates that the referent of the first argument follows the referent of the second, and a return value of 0 indicates that it does not. Otherwise the results are undefined.

os_Dumper_reference::operator !=()

os_boolean operator != (const os_Dumper_reference&) const;
Returns 1 if the arguments have different referents; returns 0 otherwise.

os_Dumper_reference::operator >=()

os_boolean operator >= (const os_Dumper_reference&) const;
If the first argument and second argument refer to elements of the same array or one beyond the end of the array, a return value of 1 indicates that the referent of the first argument follows or is the same as the referent of the second, and a return value of 0 indicates that it does not. Otherwise the results are undefined.

os_Dumper_reference::operator <=()

os_boolean operator <= (const os_Dumper_reference&) const;
If the first argument and second argument refer to elements of the same array or one beyond the end of the array, a return value of 1 indicates that the referent of the first argument precedes or is the same as the referent of the second, and a return value of 0 indicates that it does not. Otherwise the results are undefined.

os_Dumper_reference::operator !()

os_boolean operator ! () const;
Returns nonzero if the reference is null, that is, has no current referent.

os_Dumper_reference::get_database()

os_database* get_database () const;
Returns the database containing the referent of this reference.

os_Dumper_reference::get_database_number()

os_unsigned_int32 get_database_number () const;
Returns the database table number of the database containing the referent of this reference.

os_Dumper_reference::get_segment()

os_segment* get_segment () const;
Returns the segment containing the referent of this reference.

os_Dumper_reference::get_segment_number()

os_unsigned_int32 get_segment_number () const;
Returns the segment number of the segment containing the referent of this reference.

os_Dumper_reference::get_offset()

os_unsigned_int32 get_offset () const;
Returns the offset of the referent of this reference within its segment.

os_Dumper_reference::get_string()

const char* get_string () const;
Returns the reference's value as an encoded string.

os_Dumper_reference::is_valid()

os_boolean is_valid () const;
Returns nonzero if this is completely valid; returns 0 otherwise.

os_Type_info

Instances of this class hold information about the loading of the object form or fixup form currently being processed. It defines members for getting the type and predump location of the object being loaded or fixed up, as well as members for getting and setting the postload location of the object.

os_Type_info::os_Type_info()

os_Type_info (
      os_Type_loader&, 
      os_Loader_stream&, 
      os_Object_info&
);
Constructs an object to hold information about the object currently being loaded by a specified loader. You must also pass as constructor argument the info for the previous object load. See Implementing operator ()() in the section on specializing os_Type_loader.

os_Type_info::get_original_location()

os_Dumper_reference get_original_location () const;
Returns a reference that resolves to the predump location of the object being loaded. See Implementing create() in the section on specializing os_Type_loader.

os_Type_info::get_replacing_location()

os_Dumper_reference get_replacing_location () const;
Returns a reference that resolves to the postload location of the object being loaded. See Implementing create() in the section on specializing os_Type_loader.

os_Type_info::set_replacing_location()

void set_replacing_location (os_Dumper_reference location);
Records the location at which the postload object has been created. See Implementing create() in the section on specializing os_Type_loader.

os_Type_info::get_type()

const os_type& get_type () const;
Returns a reference to an os_type that represents the type of the object being loaded. See Implementing create() in the section on specializing os_Type_loader.

os_Type_info::get_replacing_segment()

os_segment& get_replacing_segment () const;
Returns a reference to the postload segment of the object being loaded.

os_Type_info::get_replacing_database()

os_database& get_replacing_database () const;
Returns a reference to the postload database of the object being loaded.

os_Fixup_dumper

This class serves as base class for your customized fixup-form dumpers. See Specializing os_Fixup_dumper. Some of the members of your specialization might have to call functions defined by the base class, os_Fixup_dumper. These functions are described here.

os_Fixup_dumper::os_Fixup_dumper()

os_Fixup_dumper (
      os_Dumper_stream&, 
      os_Dumper&, 
      const os_class_type&, 
      const os_Dumper_reference object_to_fix,
      unsigned number_elements = 0
);
Constructs a fixup dumper. The constructors for your customized fixup dumpers pass arguments to this constructor.

os_Fixup_dumper (const os_Fixup_dumper&);
Copy constructor.

os_Fixup_dumper::get_object_to_fix()

os_Dumper_reference get_object_to_fix () const;
Returns a dumper reference to the object for which this dumps a fixup form. See Implementing dump_info() in the section on specializing os_Fixup_dumper.

os_Fixup_dumper::get_type()

os_type &get_type() const;
Returns a reference to an os_type that represents the type of the object for which this is a fixup dumper. See Implementing dump_info() in the section on specializing os_Fixup_dumper.

os_Fixup_dumper::~os_Fixup()

virtual ~os_Fixup ();
Virtual destructor

os_Fixup_dumper::get_number_elements()

unsigned get_number_elements () const;
If this is a fixup for an array, returns the number of elements to be fixed up. Returns 0 otherwise.



[previous] [next]

Copyright © 1997 Object Design, Inc. All rights reserved.

Updated: 03/31/98 15:30:45