
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 Tags
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
Bridgebridge-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
ProcessorregisterProcessors() 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
Calculatorkeyvalue/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 CommandThen 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::ValueCalculator<tag::Foo>::getValue(constDataSet&data) const;
which
processes DataSetdata 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 keyvalueSimilarly 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::Loggerkeyvalue/bridge/processor/Logger.cpp.) This is not a
and then, we only
have:Command
#define TAG Logger
Additionally the macro OBJECT_TYPE is
#defined to be the type of object built:
#define OBJECT_TYPE ::keyvalue::logger::LoggerNotice 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 s. In our example,
the CalculatorBuilder 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::LoggerDataSet 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::PtrTraitsObjectType.
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
#defined 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 BuilderBUILDS_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.
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::TraitsElementType is a basic type, then
will be selected;
else if key::NoMapElementType is an enum
then will be chosen.
Otherwise, key::FlagMap will be
selected because the KeyValue assumes
key::ObjectMapElementType 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 stringOutputType
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::TraitsMapType is
. In this case,
the key::ObjectMapConverterType 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::StdVectorConverterType<ElementType>
is .key::StdMatrix<ElementType>