This section covers some basic aspects of KeyValue's design. The material is kept at the minimum just enough to give the reader all he/she needs to use KeyValue.
All KeyValue classes, functions, templates, etc. belong to
namespace ::keyvalue
.
Inspired by spreadsheet applications, KeyValue uses five basic types:
bool;
double;
ptime
; and
string
.
The first two types above are C++ built-in types. The other three are library types.
KeyValue introduces value::Nothing
to represent empty
data.
To maximize portability, KeyValue uses
::std::string
and
::boost::posix_time
for strings and times,
resp. These types are exported to namespace ::keyvalue
where they are called
string
and ptime
resp.
The single-value and multi-type container for basic types is
value::Variant
.
Values assigned to keys are not always single value::Variant
object. They may be
containers of value::Variant
objects as well.
KeyValue provides three such containers:
Actually, bridge and core library developers do not need to care
about the containers above. Indeed, they are used exclusively inside
KeyValue and, at some point, are converted to more standard types.
More precisely, a value::Single
object is converted
into an appropriate basic type T while a value::Vector
object becomes a
::std::vector<T>
and a value::Matrix
is transformed into a
::std::vector<std::vector<T> >
object.
Class value::Value is a single-value and multi-type
container for value::Single
, value::Vector
or value::Matrix
objects.
Only value::Value objects are returned from KeyValue
to front-ends. Hence, a series of conversions must be performed when
one wants to return a more basic type. For instance, suppose that a
double value x
must be returned. In
that case the sequence of conversions would be:
returnvalue::Value
(value::Single
(value::Variant
(x)));
Statements like the one above would be needed very often, which is very annoying. Fortunately, KeyValue implements a hierarchy tree of types that allow for multi-level implicitly conversions. Therefore, in the example above, the simpler statement
return x;
would be implicitly converted by the compiler into the one previously shown.
The hierarchy of types constitutes a tree where each node is
defined by a specialization of template struct Parent
.
Initially a key is just a text labeling a value. However, there
is more inside a key that just an string
object
can model. Consider the key Dates in the
introductory example again. Its associated value is expected to verify
certain conditions:
The corresponding value::Value
object is made of
ptime
object(s) rather than, say,
double ones.
Given the plural in Dates, one can
expect more than one ptime
and, then
value::Value
's content might be
a value::Vector
(of
ptime
s).
Since to each date corresponds a stock price, those dates cannot be in the future.
Additionally, one can expect the dates to be in increasing order.
This kind of information is encapsulated by a certain class. In
KeyValue terminology, those classes are called real keys and belong to namespace ::keyvalue::key
.
The class key::Key
is the base of all real
keys. More precisely, real keys derive from key::Traits
which derives from
key::Key
.
Actually, key::Traits
is a template class
depending on a few parameters:
Type parameter which defines the type of elements in the
output container. It can be bool,
double, ptime
,
string
, classes defined by the core
library, etc.
This template template parameter[2] defines the class responsible to convert the input value container into a more appropriate type for core library's use. (See Section 12.3.1.)
This template template parameter[2] tells how each
value::Variant
object in the
input container must be mapped into an
ElementType object. (See Section 12.3.2.)
Conversions between KeyValue containers value::Single
, value::Vector
or value::Matrix
to more standard
types are responsibility of container
converter classes.
KeyValue provides three such templates (described below) depending on a parameter ElementType.
key::StdSingle
<ElementType>
Converts from value::Single
into
ElementType.
key::StdVector
<ElementType>
Converts from value::Vector
to
::boost::shared_ptr<std::vector<ElementType>
>
.
key::StdMatrix
<ElementType>
Converts from value::Matrix
to
::boost::shared_ptr<std::vector<std::vector<ElementType>
> >
.
If the core library uses non-standard containers, then bridge developers have two choices. They can either use the converters above as a first step and then convert again to desired types; or they can implement new container converters that produce the desired types directly from KeyValue containers. The second option is clearly more efficient.
To implement new container converters, reading the reference documentation of the three container converters above it strongly advised. Moreover, their implementations can serve as samples for implementing new ones.
Similarly to lexical conversions but depending on the key,
sometimes, each element of the input container must be mapped to a
special value. For instance, for a key Month,
it may be convenient to map string
s
"Jan
", "Fev
", ..., "Dec
" into
numbers 1, 2, ...,
12. This is an example of key::FlagMap
.
Mappings are performed by classes which implement a method to
convert from a value::Variant
objects into other
types of objects. Actually, they are template classes depending on a
parameter named OutputType which defines (but not
necessarily matches) the real output type. The real output type
might be recovered through the member type
OutputType_.
The map template classes are the following:
key::NoMap
<OutputType>
Through this map, a value::Variant
object
holding a value x is mapped into an
object of type OutputType which has the same
lexical value as x. Only front-end
enabled lexical conversions are considered. For instance, a
value::Variant
object
holding either the double
10.1 or the
string
"10.1
" is mapped
into the double (OutputType in
this case) 10.1.
key::FlagMap
<OutputType>
Some string
values are accepted
others not. The accepted ones are mapped into particular
values of type OutputType. In the example of
key Month above,
OutputType can be double,
unsigned int or an enum
type.
key::PartialMap
<OutputType>
Half way between key::NoMap
and
key::FlagMap
. First, like
key::NoMap
, considering
front-end enabled lexical conversions, it tries to map a
value::Variant
value into
an object of type OutputType which has the same
lexical value as x. If it fails, then,
like key::FlagMap
, it tries to
map a string
into a corresponding
value of type OutputType. For instance, the
value for NumberOfEdges (of a regular
polygon) must be an unsigned int greater than
2. For some special values (not all)
there correspond a polygon name (e.g.
"Triangle
" for 3 or
"Square
" for 4). There is
no special name for a regular polygon with
1111 edges.
key::ObjectMap
<OutputType>
This is a map where a string
identifier is mapped into a
::boost::shared_ptr<OutputType>
pointing to an object of type OutputType.
Notice that this is the only map where
OutputType and OutputType_
differ.
There are some basic properties shared by several types of keys. For instance, Price, Weight, Size, etc., accept only positive numeric values. Although one can implement one class for each of them, this would imply extensive code duplication. Therefore, KeyValue implements a few generic keys which can be used for those having basic properties. Only very specific and application dependent keys need to be implemented as new real keys.
All generic keys set their label at construction time. They are:
key::Single
<ElementType>
Key for a single object of type ElementType. No constraints on the value are set.
Example: A key labeled Number
which
accepts any double value is defined
by
key::Single
<double> key("Number");
key::Vector
<ElementType>
Key for a vector of objects of type ElementType with no constraints on them. A restriction on the size of the vector might be set on construction.
Example: A key labeled Names
which
expects a vector of 5 strings is defined
by
key::Vector
<string> key("Names", 5);
key::Matrix
<ElementType>
Key for a matrix of objects of type ElementType with no constraints. A restriction on the matrix dimension can be set at construction.
Example: A key labeled Transformation
which accepts a 2x3 matrix is defined
by
key::Matrix
<double> key("Transformation", 2, 3);
key::Positive
Key for a positive number.
Example: A key labeled Price
is defined
by
key::Positive
key("Price");
key::StrictlyPositive
Key for a strictly positive number.
Example: If the key in the previous example could not accept the value 0, then it would be defined by
key::StrictlyPositive
key("Price");
key::Bounded
<ElementType,
Bound1, Bound2>
Key for a single bounded value of type
ElementType. Template template
parameters[2]
Bound1 and Bound2 define the bound
types and can be either key::Greater
,
key::Geq
(greater than or
equal to), key::Less
or
key::Leq
(less than or
equal to).
Example: A key labeled Probability
accepting any double value from and including
0 up to and including
1 is defined by
key::Bounded
<double,key::Geq
,key::Leq
> key("Probability", 0.0, 1.0);
key::MonotoneBoundedVector
<ElementType,
Monotone, Bound1, Bound2>
Key for vectors whose elements are monotonic and/or
bounded. Template template parameter[2]
Monotone
defines the type of monotonicity and
can be either key::NonMonotone
,
key::Increasing
,
key::StrictlyIncreasing
,
key::Decreasing
,
key::StrictlyDecreasing
.
Bound1 and Bound2 are as in
key::Bounded
.
Additionally, a constraint on the vector size can be set on
construction.
Example: A key labeled Probabilities
accepting 10 strictly increasing
numbers from and excluding 0 up to and
including 1 is defined
by
key::Bounded
<double,key::StrictlyIncreasing
,key::Greater
,key::Leq
> key("Probabilities", 0.0, 1.0, 10);
Key-value pairs are stored in DataSet
s. This class implements
methods getValue()
and find()
to retrieve values assigned
to keys. Both methods receive a real key and processes all the
information about the expected value encapsulated by the key. For
instance, suppose the variable today
holds the
current date and consider a key BirthDates which
corresponds to a vector of increasing dates, supposedly, in the past
or today.
An appropriate real key is then:
key::MonotoneBoundedVector
<ptime,key::Increasing
,key::Leq
> births("BrithDates", today);
Therefore, if key BirthDates belongs to a
DataSet
data
, the
result of
data.getValue
(births);
is
a ::boost::shared_ptr
<const
::std::vector<ptime> >
such that the elements of
the pointed vector are in increasing order and before (less than or
equal to) today
. The caller does not need to check
that.
Since the type returned by getValue()
depends on the real key
it receives, this method is a template function. The same is true for
find()
.
The difference between getValue()
and find()
concerns what happens when
the key is not resolved. The former method throws an exception to
indicate the failure while the latter returns a null pointer. In
practice, getValue()
is used for compulsory
keys and find()
for optional ones. A typical
use of find()
follows:
bool foo(false); if (bool* ptr = data.find
(key::Single
<bool>("Foo"))) foo = *ptr;
In the code above foo
is
false
unless key Foo is found in
data
, in which case, foo
gets
the given value.
All builders and calculators derive from class Processor
. This class declares two
pure virtual methods: getName()
and getResult()
. The former method
returns the name under which the processor is recognized by key
Processor. The second gets the result of
processing a DataSet
.
Actually, builders and calculators are specializations of
template classes Builder
and Calculator
, resp. They depend on a
parameter type named either OutputType (for
Builder
) or Tag
(for Calculator
). The primary role of
these parameters is to distinguish between different specializations.
For a Builder
,
OutputType also defines the type of object
built.
Although a Builder
specialization is uniquely
identified by OutputType, it also needs to be
assigned to a Tag for a unified registration
process of Builder
s and Calculator
s into the global
ProcessorMngr
. (See Section 13 for more information on this
registration process.)
Builder
and Calculator
specializations may
implement different features which define their declaration and
implementation. Therefore, different specializations of Builder
(or of Calculator
) might derive from
different base classes and implement different methods. Rather than
declare the specializations from scratch, providing all its base
classes and declaring all its methods, the helper files
keyvalue/mngt/DeclareBuilder.h
and
keyvalue/mngt/DeclareCalculator.h
should be used.
(See Builder
and Calculator
for details).
Builder
and Calculator
specializations might be
Command
s as explained below.
A Processor
able to process an empty
DataSet
might be declared a
Command
.
For instance, if the Processor
is
,
for some ObjectType, then
Builder
<ObjectType>
might do its job ignoring
Builder
<ObjectType>::getObject
(const DataSet
&
data
)data
(which is the case of
ListOfDataSets and
NumberOfDataSets). Another way is when the
method does look up values in an empty data
but,
failing to find any, can still do its job considering default values
for the searched keys (with or without intervention of
Default DataSet
(see Section 9).
In either cases above,
might be declared a Builder
<ObjectType>Command
by publicly deriving from
this class. Similar arguments hold for a
.Calculator
<Tag>
When a Processor
is a Command
some front-ends might take
advantage of this fact and provide to their users some shortcut or
menu entry to call the Processor
without asking for
additional user's input.
In general, the information that processors need to perform
their duties is so rich that must be stored in a DataSet
. Nevertheless, in some
particular cases, a single value::Variant
might be enough. For
instance, consider a builder that creates a curve given a few points
on it. Normally, this processor needs the set of points together
with interpolator and extrapolator methods. In this general case, a
DataSet
is necessary to hold all
this information. However, when the curve is known to be constant,
then a single number - the constant - is enough. Rather than
creating a DataSet
to store a single number,
it would be more convenient if the processor could accept just this
value (or more generally, a value::Variant
). This is, indeed,
the case.
Any Builder
specialization which can
build from a value::Variant
object must derive
from template class BuilderFromVariant
. Additionally,
it also must derive from at least one instantiation of template
class BuilderFrom
.
The abstract class Message
defines the interface for all
types of messages. MessageImpl
is a template class which
implements Message
's pure virtual methods. There
are six different specializations of MessageImpl
with corresponding
typedefs:
They define operator&()
to append formated
data to themselves. A typical use follows:
Info
info(1); // Create a level-1 Info message.
size_t i;
std::vector<double> x;
//...
info & "x[" & i & "] = " & x[i] & '\n';
Similarly, exception::Exception
is an abstract
class whose pure virtual methods are implemented by template class
exception::ExceptionImpl
. This
template class has a member which is an instantiation of
MessageImpl
. The exact instantiation
is provided as a template parameter of exception::ExceptionImpl
. There are
two specializations of exception::ExceptionImpl
with
corresponding typedefs:
RuntimeError
(having an
Error
member); and
LogicError
(having a
Logic
member).
RuntimeError
indicates errors that
can be detected only at runtime depending on user data.
LogicError
indicates errors that
should be detected at development time. In other terms, a
LogicError
means a bug and is thrown
when a program invariant fails. It is mainly used through macro
KV_ASSERT
as in
KV_ASSERT
(i < getSize(), "Out of bound!");
To keep compatibility with exception handlers catching standard
exceptions, RuntimeError
derives from
::std::runtime_error
while LogicError
derives from
::std::logic_error
.
Method exception::ExceptionImpl
::
operator&()
provides the same
functionality of MessageImpl
::
operator&()
.
Example:
if (price <= 0.0)
throw RuntimeError
() & "Invalid price. Expecting a positive number. Got " &
price;
Other more specific exception classes are implemented to
indicate errors that need special treatment. They all derive from
either RuntimeError
or LogicError
.
[2] Template template parameters belong to the less
known features of C++ and then deserve a quick note here.
Most template parameters are types. Nevertheless,
sometimes a template parameter can be a template, in which
case it is referred as a template
template parameter. For instance, a template
Foo
depending on only one template
template parameter might be instantiated with
Foo<std::vector>
but not with
Foo<std::vector<int> >
.
Recall that std::vector
is a
template class while
std::vector<int>
is a
class.