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.
Bridge
:This class provides information about the core library, e.g., its name and greeting messages. (See Section 13.1.)
The bridge implements a certain number of processors to be called by users through key Processor. (See Section 13.2.)
The global
(accessible
through template ProcessorMngr
) is responsible for
retrieving a processor provided its name. Therefore, every
Global
must register
itself into the global Processor
at
KeyValue's initialization.ProcessorMngr
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 Tag
s
that identify the various processors.
To register all processors, simply #include
header file registerProcessor.h
and call
function
. The
suggested place to make this call is the registerProcessors
()
constructor (see
Section 13.1).Bridge
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.
KeyValue comes with a few generic keys but other application specific keys can be implemented. (See Section 13.3.)
Some methods of class
are implemented by
KeyValue itself. However there are four public methods which are left to
the bridge developer. (See example in
Bridge
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,
s registration is
suggested to be launched from here by calling function
Processor
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.
and
Builder
specializations (the
two flavors of Calculator
) are implemented in
similar ways. Firstly, let us see how to implement the latter and then
cover the differences for the former.Processor
As previously said, to get the proper declaration of a
, the helper file
Calculator
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
, we
Command
#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 this example). 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.
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
. For example, when an
empty Command
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 DataSet
"Reset repository"
seems more
appropriate to appear in a menu.
The last method is
value::Value
Calculator
<tag::Foo
>::getValue
(constDataSet
&data
) const;
which
processes DataSet
data
and returns a
. 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.value::Value
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
or
data
.getValue
()
,
data
.find
()
must call
Calculator
<tag::Foo>::getValue
()
which returns
data.
mustUpdate
()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
If appropriate, the
can bypass this
memoization system by simply not calling
Calculator
. For instance,
if a data.
mustUpdate
()
returns the current
time, then it should rather avoid the memoization system otherwise it
will always return the same time.Calculator
Finally we close namespace ::keyvalue
:
} // namespace keyvalue
Similarly to
s, we
Calculator
#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
" // ... namespacekeyvalue
{
Macros TAG
and COMMAND
are used in the
same way as for
s. For the sake of
concreteness, consider the Calculator
specialization for
Builder
. (See
logger::Logger
keyvalue/bridge/processor/Logger.cpp
.) This is not a
and then, we only
have:Command
#define TAG Logger
Additionally the macro OBJECT_TYPE
is
#define
d 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
#include
d:
#include "keyvalue/mngt/DeclareBuilder.h
"
Methods that return the processor- and (possibly) command- names
are implemented as per
s. In our example,
the Calculator
Builder
is not a
then, only
Command
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
from the input
logger::Logger
DataSet
and return a pointer to it
is:
value::PtrTraits
<::keyvalue::logger::Logger
>::Type_Builder
<tag::Logger
>::getObject
(constDataSet
&data
) const;
Notice
the returned type. This is simpler than it appears. Indeed, in this
example the returned type is just an alias to
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 ::boost::shared_ptr
<::keyvalue::logger::Logger
>
is a helper
that sets the correct smart pointer depending on the
value::PtrTraits
ObjectType
.
The memoization works for
similarly as per
Builder
<tag::Logger
>::getObject
()
: If
Calculator
<tag::Foo
>::getValue
()
returns
data
.mustUpdate
()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
#define
d if the
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 Builder
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
#include
d.
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
. For
instance,key::Traits
namespacekeyvalue
{ namespacekey
{ classMyKey
: publicTraits
<double,StdSingle
,NoMap
> { // ... }; } // namespacekey
} // namespacekeyvalue
is the
prototype for a key accepting a single double
value which is not mapped. (Actually, the second and third parameters of
above match the
default choices and then, could be omitted.)key::Traits
The choices of
parameters impose
some methods to be implemented by derived classes. Those methods are
divided in two categories: mapping- and checking- methods.key::Traits
The third parameter of
, 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.key::Traits
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
' parameter
key::Traits
ElementType
is a basic type, then
will be selected;
else if key::NoMap
ElementType
is an enum
then
will be chosen.
Otherwise, key::FlagMap
will be
selected because the KeyValue assumes
key::ObjectMap
ElementType
is a type defined by the core
library and for which a
specialization is
implemented.Builder
Map types
and
key::NoMap
do not impose
any constraint on key::ObjectMap
derived classes.
Luckily, the constraint imposed on key::Traits
derived classes
when either key::Traits
or
key::FlagMap
is selected is
a matter of implementing just one method with the following
signature:key::PartialMap
OutputTypeget
(conststring
&name
) const = 0;
Here,
OutputType
matches parameter
ElementType
used to instantiate
. This method
receives a key::Traits
object and maps
it to the correct value of type string
OutputType
or throws a
to report
failure.RuntimeError
The checks performed on the output of DataSet
::
depend on its type.
For instance, one can check the size of a vector but not that of a
single. Regardless the getValue
()ConverterType
,
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,
key::Traits
exception must be
thrown.RuntimeError
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
. The only
exception is when the key::Traits
MapType
is
. In this case,
the key::ObjectMap
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:
voidcheckOutput
(const OutputType_&output
) const;
key::StdVector
<ElementType>
In this case, OutputType_
is
.
The method that validates the output has the same signature as
the one of ::boost::shared_ptr
<::std::vector
<ElementType>>
seen
above.key::StdSingle
There is a method to check the vector size. It is declared as follows:
voidcheckSize
(size_tsize
) 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
voidcheckSoFar
(const ConverterType<ElementType>&container
) const;
Notice
that it receives a
ConverterType<ElementType>
which, in this case, is
.
This type provides accessor methods to the output vector under
construction.key::StdVector
<ElementType>
key::StdMatrix
<ElementType>
Here OutputType_ is
.
The method that validates the output has the same signature as
the one of ::boost::shared_ptr
<::std::vector
<::std::vector
<ElementType>>>
seen
above. The method for checking the matrix dimensions
iskey::StdSingle
void checkSize(size_tnRows
, size_tnCols
) const;
Finally, a method for checking the output as the
computation runs has the same signature as the one of
but,
naturally, here
key::StdVector
ConverterType<ElementType>
is
.key::StdMatrix
<ElementType>