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.
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 implement a certain number of processors to be called by users through key Processor. (See Section 13.2.)
The class ProcessorMngr
is responsible for
retrieving a processor provided its name. Therefore, every processor
must be known by this class which is done by including the
processor's address into its array member ProcessorMngr::processors_
.
The suggested registration method is
as follows. 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 other source files.
File Register.cpp
is left as it is while file
Register.h
is edited (see instructions there
in) to list names of pointers pointing to the various
processors.
Implementing keys:
KeyValue comes with generic keys (e.g.
key::Positive
, which is used for
keys whose values are positive numbers). 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.
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.
Basic calculators (those that cannot process value::Variant
s) 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
(constDataSet
& 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()
:
namespacekeyvalue
{ namespacebridge
{Processor
* calculatorTag = &Calculator
<Tag>::getInstance
(); } }
Then, this pointer must be listed in the header file
Register.h
as explained there in.
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
(constvalue::Variant
& data) const;
This
methods processes value::Variant
data
and overriding the implementation provided
by Calculator
<Tag>
(which throws an exception).
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
.
namespacekeyvalue
{ namespacebridge
{Processor
* builderObjectType = &Builder
<ObjectType>::getInstance
(); } }
The method which builds from a DataSet
has the following
signature:
template <> shared_ptr<ObjectType>Builder
<ObjectType>::getObject
(constDataSet
& 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
(constDataSet
& data) const { // ... if (data.
mustUpdate
()) return shared_ptr<Polygon>(new Regular(nEdges, size)); // ... return shared_ptr<Polygon>(); }
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
(constvalue::Variant
& data) const;
For this method the memoization explained above does not apply
since there is no method value::Variant
::mustUpdate()
.
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 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>
.
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
.