KeyValue
User's Manual - version: 0.2

13. How to implement the bridge library

The bridge library connects KeyValue with the core library. KeyValue comes with an example bridge which can be used as a sample for bridge developers.

The implementation of a bridge library is composed of three tasks.

13.1. How to implement class Bridge

Some methods of class Bridge are implemented by KeyValue itself. However there are three public methods which are left to the bridge developer. (See example in bridge-example/bridge-example/Bridge.cpp):

const char*
getCoreLibraryName() const;

Returns the name of the core library. The result also names the function called in OpenOffice Calc or Excel spreadsheets and, for that reason, must be a single word (no white spaces). Otherwise front-ends might get in trouble.

const char*
getSimpleInfo() const;

Returns a simple description (one line long) of the core library. This message is used by Excel add-in manager.

const char*
getCompleteInfo() const;

Returns a more detailed description of the core library. This message is presented by loggers when they are initialized.

13.2. How to implement a processor

Builder and Calculator specializations (the two flavors of Processor), are implemented in similar ways. Firstly, let us see how to implement the latter and then cover the differences for the former.

13.2.1. Implementing a Calculator specialization

As previously said, to get the proper declaration one should use the helper file keyvalue/mngt/DeclareCalculator.h.

After having #included the required header files (notably, keyvalue/mngt/Calculator.h) and open the namespace keyvalue:

#include "keyvalue/mngt/Calculator.h"
// Include other required header files

namespace keyvalue {

we #define the macro TAG to be a word that uniquely identifies the specialization to be implemented. For sake of concreteness, say this word is Foo. Then we do

#define TAG Foo

Now, provided the specialization is a Command we #define the macro COMMAND:

#define COMMAND // Must be defined if, and only if, the calculator is a Command

Then the helper file is included:

#include "keyvalue/mngt/DeclareCalculator.h"

The steps above provide the correct declaration of the specialization. In the example, it is Calculator<tag::Foo>. (It is worth mentionning that keyvalue/mngt/DeclareCalculator.h will declare a type Foo in namespace tag.)

Now we implement a few methods. The first one is

const char*
Calculator<tag::Foo>::getName() const;

which returns the name to be assigned to key Processor when the user wants to call this specialization.

Provided the specialization is a Command, the following method is implemented

const char*
Calculator<tag::Foo>::getCommandName() const;

It returns an alternative name which front-ends might use when calling this specialization as a Command. For example, when an empty DataSet is given to processor DeleteDataSets the whole repository is cleared. Since this is exactly what happens when processor DeleteDataSets is called as a command, then the name "Reset repository" seems appropriate to appear in a menu.

The last method to be implemented is

value::Value
Calculator<tag::Foo>::getValue(const DataSet& data) const;

which processes DataSet data and returns a value::Value computed based on data's key-value pairs. Recall that value::Value belongs to the hierarchy of types which allows for multi-level implicit conversions. (See Section 12.2.1.) Therefore, any type below value::Value in the hierarchy might be returned without further ado.

KeyValue implements a memoization system to prevent recalculating when the input key-value pairs in data have not changed since last call. To use this feature, after having retrieve all values by calling data.getValue()or data.find(), Calculator<tag::Foo>::getValue() must call data.mustUpdate() which returns true if the value must be recalculated (or false, otherwise). If the value does need to be recalculated, then Calculator<tag::Foo>::getValue() computes the value and returns it. Otherwise, Calculator<tag::Foo>::getValue()must return a default constructed value::Value.

Finally we close namespace ::keyvalue

} // namespace keyvalue

13.2.2. Implementing a Builder specialization

The first difference in respect to implementing a Calculator specialization is the helper file to be used: keyvalue/mngt/DeclareBuilder.h.

Macros TAG and COMMAND are used exaclty as before. Additionally the macro OBJECT_TYPE must be #defined as the type of object built by the specialization. Consider the example of the processor Logger. (See keyvalue/bridge/processor/Logger.cpp.) We have

#define OBJECT_TYPE logger::Logger

Notice that we provide the fully qualified typename (as recognized from namespace ::keyvalue). For Builders, in contrast to Calculator specializations, it is this typename (rather than the one given by macro TAG) which parametrizes the template class. Therefore, in the example, the methods to be implemented are members of Builder<logger::Logger>.

Other four macros might be #defined depending on the Builder being able to build the object from a single value. Those macros are BUILDS_FROM_BOOL, BUILDS_FROM_DOUBLE, BUILDS_FROM_PTIME and BUILDS_FROM_STRING. To each of these macros corresponds a method which the user must implement when the macro is defined. For instance, if BUILD_FROM_DOUBLE is defined, then one must provide the implementation of the following method

shared_ptr<ObjectType>
Builder<ObjectType>::getObject(const bool& data) const;

where ObjectType is the same type used to #define OBJECT_TYPE.

Its worthing remember that all macros must be defined before we #include keyvalue/mngt/DeclareBuilder.h.

Recall that one must also implement the method

shared_ptr<ObjectType>
Builder<ObjectType>::getObject(const DataSet& data) const;

13.3. How to implement a key

Key functionalities belong to namespace ::keyvalue::key and all keys should be in this namespace as well.

The basics for implementing keys were explained in Section 12.3. In particular, we have seen that all keys derive from template key::Traits. For instance,

namespace keyvalue {
  namespace key {
    class MyKey : public Traits<double, StdSingle, NoMap> {
      // ...
    };
  }
}

is the prototype for a key accepting a single double value which is not mapped. (Actually, the second and third of key::Traits' parameters above are the default choices and, then, could be omitted.)

The choices of key::Traits parameters impose some methods to be implemented by derived classes. Those methods are divided in two categories: checking- and mapping- methods.

13.3.1. Checking methods

The third parameter of key::Traits, namely MapType, is a template class which defines the type of output value, OutputType_. More precisely, this is the type that one gets when passes the key (i.e., the class derived from key::Traits) to DataSet::getValue().

The checks performed on the output value depend on its type. For instance, one can check the size of a vector but not that of a single. Regardless the ConverterType, key::Traits implements all required checking methods. Actually, the provided implementations accept all values (no check at all). When the developer wants a proper check, then the corresponding method can be overridden. To indicate invalid values RuntimeError exception must thrown.

KeyValue implements three templates that can be assigned to ConverterType. They depend on a type parameter ElementType which, in general, matches its homonym provided to key::Traits. The only exception is when the MapType parameter given to key::Traits is key::ObjectMap. In this case, the ConverterType is instantiated for ::boost::shared_ptr<ElementType>.

key::StdSingle<ElementType>

This is the default choice and can be omitted when key::Traits' third parameter is so.

For this choice OutputType_ is the same as ElementType and the method called to validate the output has the following signature:

void
checkOutput(const OutputType_& output) const;
key::StdVector<ElementType>

In this case, OutputType_ is ::boost::shared_ptr<std::vector<ElementType> >. The method that validates the output has the same signature as for key::StdSingle seen above.

There is a method for checking the vector size, declared as follows:

void
checkSize(size_t size) const;

Additionally, there is a method to check the output while it is still being calculated. This is useful to indicate errors as earlier as possible. For instance, consider a vector which is expected to have a huge number of increasing elements. If the second element is not greater than the first one, the method can immediately spot the problem and avoid to process from the third element onwards. The signature is

void
checkSoFar(const ConverterType<ElementType>& container) const;

Notice that it receives a ConverterType<ElementType>, which in this case, is key::StdVector<ElementType>. This type provides accessor methods to the output vector being constructed.

key::StdMatrix<ElementType>

Here OutputType_ is ::boost::shared_ptr<std::vector<std::vector<ElementType> > >. The method that validates the output has the same signature as for key::StdSingle seen above. The method for checking the matrix dimensions is

void
checkSize(size_t nRows, size_t nCols) const;

Finally, a method for checking the output as the computation runs has the same signature as for key::StdVector seen above. However, here ConverterType<ElementType> is key::StdMatrix<ElementType>.

13.3.2. Mapping methods

Third parameter of key::Traits, namely MapType, defines the type of mapping as introduced in Section 12.3.2. The value assigned to this parameter should be a template class chosen among four possibilities.

In many circumstances, MapType do not need to be explicitly provided by the user since the compiler can automatically deduce it. The choice follows a simple rule: if key::Traits parameter ElementType is bool, double, string, ptime or unsigned int, then key::NoMap will be selected; else if ElementType is an enum then key::FlagMap will be choosen. Otherwise, key::ObjectMap will be selected because the KeyValue assumes ElementType is a type defined by the core library and for which a Builder specialization is implemented.

Map types key::NoMap and key::ObjectMap do not impose any constraint on key::Traits derived classes. Luckily the constraint imposed on key::Traits derived classes when either key::FlagMap or key::PartialMap is selected is just a matter of implementing one method with the following signature:

OutputType
get(const string& name) const = 0;

Here, OutputType is the same as the parameter ElementType used to instantiate key::Traits. This method receives a string object and maps it to the correct value of type OutputType or, if it fails, throws a RuntimeError.

Valid HTML 4.01 TransitionalValid CSS!