KeyValue
User's Manual - version: 0.1

13. How to implement the bridge library

The bridge library connects KeyValue and core libraries. KeyValue comes with an example bridge which can be used as a model 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

The two types of processors, builders and calculators, are implemented in similar ways. Let us see how to implement a calculator and then the differences for builders.

13.2.1. Implementing a calculator

Basic calculators (those that cannot process value::Variants) are specializations of template class Calculator. (See example in bridge-example/processor/Area.cpp.)

Each specialization is uniquely identified by a type Tag which, normally, is a forward-declared-only class:

class Tag;          

Given the type Tag, developers must implement two of Calculator<Tag>'s methods:

template <>
const char* Calculator<Tag>::getName() const;

Returns the name under which Calculator<Tag> is recognized by key Processor.

template <>
value::Value
Calculator<Tag>::getValue(const DataSet& data) const;

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.

As explained earlier, the processor must be registered into the ProcessorMngr. The suggested method needs a pointer to Processor in namespace ::keyvalue::bridge pointing to the instance of Calculator<Tag> returned by Calculator<Tag>::getInstance():

namespace keyvalue {
  namespace bridge {
    Processor* calculatorTag = &Calculator<Tag>::getInstance();
  }
}

Then, this pointer must be listed in the header file Register.h as explained there in.

13.2.2. Implementing an extended calculator

Extended calculators are specializations of template class XCalculator. Given a type Tag, XCalculator<Tag> derives from Calculator<Tag> and all the instructions above to implement Calculator<Tag> apply. Additionally, the following method must be implemented:

template <>
value::Value
XCalculator<Tag>::getValue(const value::Variant& data) const;

This methods processes value::Variant data and overriding the implementation provided by Calculator<Tag> (which throws an exception).

13.2.3. Implementing a builder

Basic builders are specializations of template class Builder. (See example in bridge-example/processor/Polygon.cpp.) Similarly to Calculator, each specialization of Builder is uniquely defined by a template type parameter OutputType. Additionally, for builders this type also defines the type of object built. Normally, it will be a type defined by the core library.

Given OutputType, the method which returns Builder<ObjectType>'s name has the following signature:

template <>
const char* Builder<OutputType>::getName() const;

The registration is similar to that of a Calculator: One have to define a pointer to the unique instance of Builder<ObjectType> and list this pointer in Register.h.

namespace keyvalue {
  namespace bridge {
    Processor* builderObjectType = &Builder<ObjectType>::getInstance();
  }
}

The method which builds from a DataSet has the following signature:

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

KeyValue implements a memoization system to prevent rebuilding an object when the input key-value pairs in data have not changed since last built. To use this feature, after having retrieve all values which define the object to be build, Builder<ObjectType>::getObject() must call data.mustUpdate() which returns true if the object must be rebuilt (or false, otherwise). If the object does need to be rebuilt, then Builder<ObjectType>::getObject() builds a new object and returns a shared pointer to it. Otherwise, Builder<ObjectType>::getObject() returns a null shared_ptr<ObjectType>. For instance, in bridge-example/processor/Polygon.cpp we have (recall that Regular derives from Polygon):

template <>
shared_ptr<Polygon>
Builder<ObjectType>::getObject(const DataSet& data) const {
  // ...
  if (data.mustUpdate())
    return shared_ptr<Polygon>(new Regular(nEdges, size));
  // ...
  return shared_ptr<Polygon>();
}

13.2.4. Implementing an extended builder

Given ObjectType the extended builder for this type is XBuilder<ObjectType> which derives from Builder<ObjectType>. All the instructions above apply and, additionally, the following method, which builds from a value::Variant, must be implemented:

template <>
shared_ptr<ObjectType>
XBuilder<ObjectType>::getObject(const value::Variant& data) const;

For this method the memoization explained above does not apply since there is no method value::Variant::mustUpdate().

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 and reimplemented. 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.

Two map types when selected do not impose any constraint on key::Traits derived classes. They are key::NoMap and key::ObjectMap. Actually, they do not need to be explicitly provided by the user since the compiler will automatically select one of them when MapType is omitted. The choice between those two follows a simple rule: if key::Traits parameter ElementType is bool, double, string, ptime or unsigned int, then key::NoMap will be selected; otherwise key::ObjectMap will because the compiler assumes that ElementType is a type defined by the core library and for which a Builder specialization is implemented.

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!