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.
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
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/Register.h
and
bridge-example/Register.cpp
to their own source
directory to be compiled and linked as their own source files. File
Register.cpp
is left as it is while file
Register.h
is edited (see instructions there
in) to list the Tags that identify the various
processors.
Implementing keys:
KeyValue comes with a few generic keys but other application specific keys can be implemented. (See Section 13.3.)
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.
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.
As previously said, to get the proper declaration one should
use the helper file
keyvalue/mngt/DeclareCalculator.h
.
After having #include
d 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
.
(It is worth mentionning that
Calculator
<tag::Foo>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
(constDataSet
&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
or
data
.getValue()
,
data
.find()
must call
Calculator
<tag::Foo>::getValue()
data.
mustUpdate()
which returns
true
if the value must be recalculated (or
false
, otherwise). If the value does need to be
recalculated, then
computes the
value and returns it. Otherwise, Calculator
<tag::Foo>::getValue()
must return a
default constructed Calculator
<tag::Foo>::getValue()
value::Value
.
Finally we close namespace
::keyvalue
} // namespace keyvalue
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 #define
d 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 Builder
s, 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 #define
d 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;
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.
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>
.
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
.