Introduction
ArmarX properties are the tool in the ArmarX framework to retrieve Ice configuration properties and command line options. This tool is part of the framework core and can be considered as an extension to the plain Ice::Properties container. As for ArmarXCore the properties are integrated in armarx::Component and armarx::Application. Both implement the armarx::PropertyUser interface and offer direct access to the properties within their scope.
- Note
- We refer to configuration variables defined in a config file as properties and configuration variables passed though command line as options
Feature overview
As mentioned before, the ArmarX properties extend the Ice::Properties by several capabilities:
- Mapping config values onto objects of any desired type
- Validating config values using regular expressions
- Forcing required properties to be set
- Returning defaults for optional properties regarless the default object type
- Setting bounds for numeric properties and options
- Forcing case sensitivity on config values
- Generating formatted and detailed description of properties for
- Help output
- User manual documentation (e.g. Doxygen, Latex, HTML, etc)
- Config files automatically filled with default values (e.g. Ice.Config and XML files)
Structure overview
armarx::PropertyUser
Every component (in general manner) which aims to provide properties has to implement the armarx::PropertyUser interface.
armarx::PropertyUser basic outline
class PropertyUser
{
public:
template <typename Type> Property<Type> getProperty(string name);
};
As can be seen, the armarx::PropertyUser::createPropertyDefinitions() factory function has to be implemented. It creates an instance of the property definitions.
The armarx::PropertyUser::getProperty() function provides access to a desired property as long as it is defined.
armarx::PropertyDefinitionContainer
armarx::PropertyDefinitionContainer basic outline
class PropertyDefinitionContainer
{
public:
PropertyDefinitionContainer(std::string prefix);
template <typename PropertyType>
PropertyDefinition<PropertyType>& defineRequiredProperty(
string name,
string description);
template <typename PropertyType>
PropertyDefinition<PropertyType>& defineOptionalProperty(
string name,
PropertyType defaultValue,
string description);
template <typename PropertyType>
PropertyDefinition<PropertyType>& getDefintion(string name);
string toString(PropertyDefinitionFormatter& formatter);
};
This is the class you will extend to define your own properties.
The constructor takes a prefix as an argument. Using this prefix, you can specify the namespace (e.g. Domain, Component name, Application name) your properties belong to.
E.g. if the prefix is "ArmarX.MyComponent", then the PropertyUser will select all properties which start with "ArmarX.MyComponent." (Note the trailing dot!). A full property name is then "ArmarX.MyComponent.myFrameRate".
To define a property you may use one of the following functions:
- defineRequiredProperty()
- defineOptionalProperty()
Both functions return an armarx::PropertyDefinition instance. This is convenient to set the definition attributes immediately as shown below:
class MyDefinitions:
public PropertyDefinitionContainer
{
public:
MyDefinitions(string prefix):
PropertyDefinitionContainer(prefix)
{
defineOptionalProperty("FrameRate", 30.f, "Frames per second")
.setMin(1.f)
.setMax(60.f)
.setMatchRegex("\\d+(.\\d*)?");
}
};
Features
- Fluent interface
- Generic property type mapping
- Property requirement check
- Property value mapping validation
- Property value validation by regular expression matching
- Property min. & max. value limitation for numeric values
- Supports case sensitive and case insensitive value mapping
- Case sensitivity can be changed at any point without reinitializing the mapper
- Default value fallback on value retrieval errors
A more detailed description of each feature follows:
Fluent interface
The Fluent Interface is defined by the set of functions stated above in the outline. These functions preserve the context (the function call on the object is self-referential) and therefore can be chained. This improves the readability and allows the use of Property without declaring a named instance for each property. The following examples illustrate this:
enum BayerPatternType
{
eBayerPatternBg,
eBayerPatternGb,
eBayerPatternGr,
eBayerPatternRg
};
Using the mapper without fluent interface
Property<BayerPatternType> bayerPatternTypeProp("VisionX.Capturer.BayerPatternType", properties, eBayerPatternRg);
bayerPatternTypeProp.setCaseInsensitive(true);
bayerPatternTypeProp.map("bayer-pattern-bg", eBayerPatternBg);
bayerPatternTypeProp.map("bayer-pattern-gb", eBayerPatternGb);
bayerPatternTypeProp.map("bayer-pattern-gr", eBayerPatternGr);
bayerPatternTypeProp.map("bayer-pattern-rg", eBayerPatternRg);
BayerPatternType bayerPatternType = bayerPatternTypeProp.getValue();
Using the mapper with fluent interface
BayerPatternType bayerPatternType = Property<BayerPatternType>("VisionX.Capturer.BayerPatternType", properties, eBayerPatternRg)
.map("bayer-pattern-bg", eBayerPatternBg)
.map("bayer-pattern-gb", eBayerPatternGb)
.map("bayer-pattern-gr", eBayerPatternGr)
.map("bayer-pattern-rg", eBayerPatternRg)
.setCaseInsensitive(true)
.getValue();
The method chaining can be terminated at any time. Thus, using the fluent interface is optional as shown by the previous examples.
Generic property type mapping
The Ice::Properties support only string and integer return values without any validation, whereas the armarx::Property is able to return any type you specify for the typename PropertyType. The mapping of strings onto PropertyType values is done by means of map(string valueString, PropertyType value). The following examples maps string words onto boolean values:
bool usingFormat7Mode = Property<bool>("VisionX.Capturer.Format7Mode", properties, false)
.map("true", true)
.map("yes", true)
.map("1", true)
.map("false", false)
.map("no", false)
.map("0", false)
.getValue();
Sometimes you only need to convert a string into a numeric value or even want to get the raw string itself. The return value type depends on which value retrieval function you use. These come in three types:
- typename PropertyType requires mapping and is accessible by the following functions:
- It's specified in the template parameter: Property<PropertyType> (e.g. Property<float>(...))
- Returns one of the mapped values of the type PropertyType you specified
- Throws a MissingRequiredPropertyException if the property is not defined and set as required
- Throws a UnmappedValueException if value is not mapped.
- Throws a InvalidPropertyValueException if the syntax of the value is incorrect.
PropertyType getValueOrDefault()
- Returns one of the mapped values of the type PropertyType you specified
- Never throws. If the value is not mapped, the default value specified in constructor will be returned.
typename NumericType requires no mapping, performs a lexical cast and is accessible by the following functions:
NumericType getNumericValue<NumericType>()
- It's specified in the getNumericValue function template parameter (e.g. float frameRate = myMapper.getNumericValue<float>()).
- Returns a numeric value of the type NumericType if possible
- Throws a MissingRequiredPropertyException if the property is not defined and set as required
- Throws a ValueRangeExceededException if value exceeds the bounds
- Throws a InvalidPropertyValueException if the syntax of the value is incorrect.
NumericType getNumericValueOrDefault<NumericType>()
- Returns a numeric value of the type ''NumericType'' if possible
- Never throws. If the value not available, the default value specified in constructor will be returned.
std::string raw string property value. Requires no mapping and is accessible by the following functions:
std::string getRawStringValue();
- Returns the raw string of the value similarly to properties->getProperty("MyProperty") but checks the syntax of the value if required
- Throws a MissingRequiredPropertyException if the property is not defined and set as required
- Throws a InvalidPropertyValueException if the syntax of the value is incorrect.
std::string getRawStringValue(std::string default);
- Returns the raw string of the value similarly to properties->getProperty("MyProperty") but checks the syntax of the value if required
- Never throws. If the value is not available, the passed default value will be returned.
As you may have noticed, for each of the three value types there are two functions. One that throws error specific exceptions and one that never throws. Instead it returns a default value on errors. It is up to the developer which one of the functions shall be used.
Property requirement check
In few cases a component may require configuration values in order to proceed, assuming no default value is known at compile time. To assert that a certain property value has been defined you may tell the mapper that the property is required. If the property is not set, an exception is thrown while trying to get the value from the mapper (e.g. by calling getValue()):
try
{
std::string uidStr = Property<std::string>("VisionX.Capturer.CameraUIDs", properties, "")
.setRequired(true)
.getRawStringValue();
}
{
std::cout << "Cannot run without Camera UIDs. Terminating ..." << std::endl;
exit(1);
}
Property value mapping validation
The getValue() function will throw an armarx::exceptions::local::UnmappedValueException if the property value is not mapped. The following examples shows this case:
...
VisionX.Capturer.Format7Mode = yes
...
...
bool usingFormat7Mode = Property<bool>("VisionX.Capturer.Format7Mode", properties, false)
.map("1", true)
.map("0", false)
.getValue();
...
Since the word "yes" is not mapped, an exception is thrown. If you want to avoid the exception you may use the getValueOrDefault() function which will return the default value specified in the constructor instead of throwing an exception.
Property value validation by regular expression matching
It's in many cases easier to parse and interpret values if they match a certain pattern. For example assume we need two camera UIDs to initialize the stereo capturer. Each UID must have a length of 16 characters and consist solely of numbers and the letters A-F and finally the UIDs should be separated only by a comma or whitespace. At this point regular expressions come in handy to assert this pattern:
std::string uidStr = Property<std::string>(propPrefix + ".CameraUIDs", properties, "")
.setRequired(true)
.setMatchRegex("\\s*[a-zA-Z0-9]{16}\\s*((,|\\s)\\s*[a-zA-Z0-9]{16})")
.getRawStringValue();
Numeric property values and the min. & max. value limitation
The Property supports simple lexical cast for numeric values. If the cast failes due to wrong numeric syntax, an armarx::exceptions::local::InvalidPropertyValueException is thrown. Numeric value boundaries may be defined by the functions setMin() and setMax(). The bounds are asstered when using the getNumericValue(). If the property value is out of bounds, the armarx::exceptions::local::ValueRangeExceededException will be thrown. Again, the exceptions can be avoided by using the getNumericValueOrDefault() instead.
Support of case sensitive and case insensitive value mapping
bool usingFormat7Mode = Property<bool>("VisionX.Capturer.Format7Mode", properties, false)
.setCaseInsensitive(true)
.map("yes", true)
.map("no", false)
.getValue();
The mapper in the example above would map the words "Yes" and "YES" onto true as well, since it acts case-insensitive. This avoids many tedious mapping and permitting less restrictive inputs. The setCaseInsensitive(bool) function can be called at any time during the mapping.