KeyValue
User's Manual - version: 0.3

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.

Implementating the bridge library consists of three tasks.

Implementing class Bridge:

This class provides information about the core library, e.g., its name and greeting messages. (See Section 13.1.)

Implementing and registering processors:

The bridge implements a certain number of processors to be called by users through key Processor. (See Section 13.2.)

The global ProcessorMngr (accessible through template Global) is responsible for retrieving a processor provided its name. Therefore, every Processor must register itself into the global ProcessorMngr at KeyValue's initialization.

The suggested registration method is the following. Bridge developers copy files bridge-example/registerProcessors.h, bridge-example/registerProcessors.cpp and bridge-example/AllProcessors.h to their own source directory to be compiled and linked as their own source files. The first two files are left as they are but file AllProcessors.h should be edited (see instructions there in) to list the Tags that identify the various processors.

To register all processors, simply #include header file registerProcessor.h and call function registerProcessors(). The suggested place to make this call is the Bridge constructor (see Section 13.1).

Notice that KeyValues own's processors are listed in AllProcessors.h. Any of them can be removed from this file if one does not want make it available at runtime.

Implementing keys:

KeyValue comes with a few generic keys but other application specific keys can be implemented. (See Section 13.3.)

13.1. How to implement class Bridge

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

Bridge();

The default constructor is declared by KeyValue (not by the compiler) and, therefore, it must be implemented. One can do whatever initialization it needs for the bridge and core libraries. For instance, Processors registration is suggested to be launched from here by calling function registerProcessors() as explained above.

const char*
getCoreLibraryName() const;

Returns the name of the core library. The result also names the function called in LibreOffice 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, for instance, by the 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 of a Calculator, the helper file keyvalue/mngt/DeclareCalculator.h should be used.

Some specific header files must be included at the begining of the source code, notably keyvalue/mngt/Calculator.h. However, prior to include this file, we include the header file containing the information on the type of smart pointer used by the bridge and core libraries: (The content of this file is explained in details in Section 14.)

// First #include the header file containing smart pointer information:
// (This is just an example. Each bridge library #includes its onw file.)
#include "bridge-example/PtrTraits.h"

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

Now namespace ::keyvalue is open:

namespace keyvalue {

Then the macro TAG is set to a word that uniquely identifies the specialization. For sake of concreteness, let us assume that this word is Foo:

#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 (Calculator<tag::Foo> in this example). 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.

If the macro COMMAND is defined, then 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 out. Hence, this is exactly what happens when processor DeleteDataSets is called as a command. For this reason, the name "Reset repository" seems more appropriate to appear in a menu.

The last method is

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

which processes DataSet data and returns a value::Value. Recall that value::Value belongs to the hierarchy of types which allows for multi-level implicit conversions. Therefore, any type below value::Value in the hierarchy might be returned without further ado.

KeyValue implements a memoization system to prevent needless recalculations 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.

If appropriate, the Calculator can bypass this memoization system by simply not calling data.mustUpdate(). For instance, if a Calculator returns the current time, then it should rather avoid the memoization system otherwise it will always return the same time.

Finally we close namespace ::keyvalue:

} // namespace keyvalue

13.2.2. Implementing a Builder specialization

Similarly to Calculators, we #include required header files (notably keyvalue/mngt/Builder.h) and open namespace ::keyvalue:

// First #include the header file containing smart pointer information:
// (This is just an example. Each bridge library #includes its onw file.)
#include "bridge-example/PtrTraits.h"

// Now #include other required header files:
// ...
#include "keyvalue/mngt/Builder.h"
// ...

namespace keyvalue {

Macros TAG and COMMAND are used in the same way as for Calculators. For the sake of concreteness, consider the Builder specialization for logger::Logger. (See keyvalue/bridge/processor/Logger.cpp.) This is not a Command and then, we only have:

#define TAG Logger

Additionally the macro OBJECT_TYPE is #defined to be the type of object built:

#define OBJECT_TYPE ::keyvalue::logger::Logger

Notice that we provide the fully qualified name of the object type.

Then the helper file keyvalue/mngt/DeclareBuilder.h is #included:

#include "keyvalue/mngt/DeclareBuilder.h"

Methods that return the processor- and (possibly) command- names are implemented as per Calculators. In our example, the Builder is not a Command then, only

const char*
Builder<tag::Logger>::getName() const;

is needed. Otherwise, the following method would also be required:

const char*
Builder<tag::Logger>::getCommandName() const;

The method that builds a logger::Logger from the input DataSet and return a pointer to it is:

value::PtrTraits<::keyvalue::logger::Logger>::Type_
Builder<tag::Logger>::getObject(const DataSet& data) const;

Notice the returned type. This is simpler than it appears. Indeed, in this example the returned type is just an alias to ::boost::shared_ptr<::keyvalue::logger::Logger> and using this simpler form would equaly works. The more complicated form was used for the sake of generality. As we shall see (Section 14) KeyValue can work with different types of smart pointers and the class value::PtrTraits is a helper that sets the correct smart pointer depending on the ObjectType.

The memoization works for Builder<tag::Logger>::getObject() similarly as per Calculator<tag::Foo>::getValue(): If data.mustUpdate()returns true, then the method should return a pointer to the built object. Otherwise, it should return a default constructed pointer.

In addition to TAG, COMMAND and OBJECT_TYPE, the macro BUILDS_FROM might be #defined if the Builder is able to build from a basic type. The macro should be set to be the basic type that the Builder can build from. In this case another method must be implemented. It takes one input parameter (by const reference) of type BUILDS_FROM and returns the pointer to the object built. For instance, if BUILD_FROM were set to double, then we would have provided the implementation of

value::PtrTraits<::keyvalue::logger::Logger>::Type_
Builder<tag::Logger>::getObject(const double& data) const;

It is worth remembering that all macros must be defined before keyvalue/mngt/DeclareBuilder.h is #included.

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> {
  // ...
};

} // namespace key
} // namespace keyvalue

is the prototype for a key accepting a single double value which is not mapped. (Actually, the second and third parameters of key::Traits above match 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: mapping- and checking- methods.

13.3.1. Mapping methods

The 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 does 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 a basic type, then key::NoMap will be selected; else if ElementType is an enum then key::FlagMap will be chosen. 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 a matter of implementing just one method with the following signature:

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

Here, OutputType matches parameter ElementType used to instantiate key::Traits. This method receives a string object and maps it to the correct value of type OutputType or throws a RuntimeError to report failure.

13.3.2. Checking methods

The checks performed on the output of DataSet::getValue()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) but they can be overridden when a proper check is required. To indicate invalid values, RuntimeError exception must be 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 is key::ObjectMap. In this case, the ConverterType is instantiated for value::PtrTraits<ElementType>::Type_.

The ConverterType also defines the output type, OutputType_, returned by DataSet::getValue().

key::StdSingle<ElementType>

This is the default choice and can be omitted when MapType is so.

For this choice OutputType_ matches 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 the one of key::StdSingle seen above.

There is a method to check the vector size. It is 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 avoiding to process the third element onwards. This method's 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 under construction.

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 the one of 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 the one of key::StdVector but, naturally, here ConverterType<ElementType> is key::StdMatrix<ElementType>.

Valid HTML 4.01 TransitionalValid CSS!