Falaise  4.0.1
SuperNEMO Software Toolkit
property_set.h
Go to the documentation of this file.
1 #ifndef FALAISE_PROPERTY_SET_H
2 #define FALAISE_PROPERTY_SET_H
3 
4 #include <exception>
5 #include <string>
6 #include <vector>
8 #include "boost/mpl/contains.hpp"
9 #include "boost/mpl/vector.hpp"
10 
11 #include "falaise/config/path.h"
13 
14 namespace falaise {
15 namespace config {
16 //! Exception thrown when requesting a key that is not in the property_set
17 class missing_key_error : public std::logic_error {
18  using std::logic_error::logic_error;
19 };
20 
21 //! Exception thrown when trying to put to a key already in the property_set
22 class existing_key_error : public std::logic_error {
23  using std::logic_error::logic_error;
24 };
25 
26 //! Exception thrown when type requested in get<T>(key) does not match that of
27 //! value at key
28 class wrong_type_error : public std::logic_error {
29  using std::logic_error::logic_error;
30 };
31 
32 //! \brief Class holding a set of key-value properties
33 /*!
34  * Provides a convenient adaptor interface over datatools::properties,
35  * targeted at developers of modules for Falaise needing key-value storage
36  * and validation of input parameters. It may be used directly as
37  *
38  * ```cpp
39  * falaise::property_set ps{};
40  *
41  * // Store an integer at key "a"
42  * ps.put("a",2);
43  *
44  * // Get the integer
45  * int x = ps.get<int>("a");
46  *
47  * // Replace the integer with a double
48  * ps.put("a",3.14);
49  *
50  * // Get a string, setting a default value in case key "b" doesn't exist
51  * std::string s = ps.get<std::string>("b", "hello");
52  * std::cout << s << std::endl; // prints "hello"
53  * ```
54  *
55  * or it can be constructed from an existing instance of @ref datatools::properties
56  *
57  * ```cpp
58  * void example(datatools::properties const& x) {
59  * falaise::property_set ps{x};
60  *
61  * // ... use ps ...
62  * }
63  * ```
64  *
65  * or from a @ref datatools::properties file
66  *
67  * ```cpp
68  * void example(std::string const& file) {
69  * falaise::property_set ps{};
70  * falaise::make_property_set(file, ps);
71  *
72  * // ... use ps ...
73  * }
74  * ```
75  *
76  * Storage and retrieval of parameters is typesafe in the sense that:
77  *
78  * - You can only @ref put values that are of type
79  * - int
80  * - double
81  * - bool
82  * - std::string
83  * - @ref falaise::config::path
84  * - @ref falaise::config::quantity_t
85  * - Plus any type listed in @ref falaise_units
86  * - @ref falaise::config::property_set
87  * - std::vector<int>
88  * - std::vector<double>
89  * - std::vector<bool>
90  * - std::vector<std::string>
91  * - You can only @ref get values of the above types, and a @ref wrong_type_error
92  * will be thrown if you try to retrieve a value with a different type, e.g
93  * ``` cpp
94  * ps.put("a", 2);
95  * double x = ps.get<double>("a"); // throws wrong_type_error
96  * ```
97  *
98  * Support for @ref path and @ref quantity_t is provided to support validation
99  * of input configuration scripts. These types model the `path` and `dimension`
100  * decorators for properties parameters:
101  *
102  * ```conf
103  * # this is a true filesystem path
104  * foo : string as path = "${HOME}/something"
105  *
106  * # this is a true physical quantity with dimension "length"
107  * bar : real as length = 3.14 m
108  * ```
109  *
110  * When extracting parameters, you can ensure that that you have a true path
111  * or physical quantity of the right dimension:
112  *
113  * ```cpp
114  * void configure_me(property_set const& ps) {
115  * // throws wrong_type_error if value at "foo" is not a path
116  * auto foo = ps.get<falaise::config::path>("foo");
117  *
118  * // throws wrong_type_error if value at bar is not a physical quantity with 'length' dimension
119  * auto bar = ps.get<falaise::config::length_t>("bar");
120  *
121  * ...
122  * }
123  * ```
124  *
125  * Support for @ref put and @ref get of @ref property_set values is provided to
126  * support @ref datatools::properties constructs of the form:
127  *
128  * ```conf
129  * plain : string = "A plain property"
130  * table.foo : string = "A string property in a 'table'"
131  * table.bar : int = 42
132  * ```
133  *
134  * The `foo` and `bar` properties an be extracted either by their full keys:
135  *
136  * ```cpp
137  * auto foo = ps.get<std::string>("table.foo");
138  * auto bar = ps.get<int>("table.bar);
139  * ```
140  *
141  * or by getting the `table` key as a @ref property_set, then its properties
142  * which will be stored using their subkey names:
143  *
144  * ```cpp
145  * auto table = ps.get<falaise::config::property_set>("table");
146  * auto foo = ps.get<std::string>("foo");
147  * auto bar = ps.get<int>("bar);
148  * ```
149  *
150  * This can be extended to further levels of nesting if required. However,
151  * note that @ref datatools::properties configuration of the form:
152  *
153  * ```conf
154  * table : string = "Not a valid table"
155  * table.x : int = 11
156  * table.y : int = 22
157  * ```
158  *
159  * are not considered get-able as `property_set`s. This is due to the ambiguity
160  * of the `table` key which @ref property_set regards as an atomic property
161  * of type `std::string`. Here, the `table.x` and table.y` keys can be extracted
162  * only via their fully qualified keys. It is therefore strongly recommended that
163  * you structure your configuration in the "pure table" form above for clarity
164  * and ease of use.
165  *
166  * The default value form of @ref get can be used to implement optional configuration, for
167  * example
168  *
169  * ```cpp
170  * void configure_me(property_set const& ps) {
171  * // throws missing_key_error if ps doesn't have "foo"
172  * auto requiredParam = ps.get<int>("foo");
173  *
174  * // sets optionalParam to the value held at "answer", or 42 if ps doesn't have an "answer" key
175  * auto optionalParam = ps.get<int>("answer",42);
176  *
177  * ...
178  * }
179  * ```
180  *
181  * Combined, these various ways of using property_set allow simple and reliable usage
182  * for configuration and configuration validation in Falaise modules.
183  *
184  * \sa path
185  * \sa quantity
186  * \sa datatools::properties
187  * \sa datatools::units
188  */
190  public:
191  //! Default constructor
192  property_set() = default;
193 
194  //! Construct from an existing datatools::properties
195  /*!
196  * \param[in] ps properties
197  */
199 
200  // - Observers
201  //! Returns true if no key-value pairs are held
202  bool is_empty() const;
203 
204  //! Returns a vector of all keys in the property_set
205  std::vector<std::string> get_names() const;
206 
207  //! Returns true if the property_set stores a pair with the supplied key
208  /*!
209  * A nested key, e.g. `foo.bar`, may be supplied.
210  *
211  * \param[in] key name of key to check for existence
212  */
213  bool has_key(std::string const& key) const;
214 
215  //! Returns true if the key's value is a property/atom
216  /*!
217  * A nested key, e.g. `foo.bar`, may be supplied.
218  *
219  * \param[in] key name of the key to check
220  */
221  bool is_key_to_property(std::string const& key) const;
222 
223  //! Returns true if the keys's value is sequence
224  /*!
225  * A nested key, e.g. `foo.bar`, may be supplied.
226  *
227  * \param[in] key name of the key to check
228  */
229  bool is_key_to_sequence(std::string const& key) const;
230 
231  //! Returns true if the key's value is a @ref property_set
232  /*!
233  * A nested key, e.g. `foo.bar`, may be supplied.
234  *
235  * A key is only considered to have a @ref property_set value if
236  * it only exists as a prefix to a set of nested keys, e.g.
237  *
238  * ```
239  * pure.foo : int = 1
240  * pure.bar : int = 2
241  * pure.nested.x : int = 11
242  * pure.nested.y : int = 22
243  * ```
244  *
245  * Keys `pure` and `pure.nested` are considered as @ref property_set values.
246  * In mixed properties/values such as
247  *
248  * ```
249  * bad : int = 1
250  * bad.foo : int = 2
251  * ```
252  *
253  * `bad` is not considered as a @ref property_set.
254  *
255  * \param[in] key name of the key to check
256  */
257  bool is_key_to_property_set(std::string const& key) const;
258 
259  //! Returns a string representation of the property_set
260  std::string to_string() const;
261 
262  // - Retrievers
263  //! Return the value of type T held at supplied key
264  /*!
265  * \tparam T type of value to be returned
266  * \param[in] key key for value to find
267  * \returns value held at key
268  * \throw missing_key_error if key is not found
269  * \throw wrong_type_error if value at key is not of type T
270  */
271  template <typename T>
272  T get(std::string const& key) const;
273 
274  //! Return the value of type T associated with key, or a default if the key is
275  //! not present
276  /*!
277  * \tparam T type of value to be returned
278  * \param[in] key key for value to find
279  * \param[in] default_value value to return if key is not found
280  * \throw wrong_type_error if key is found and associated value is not type T
281  */
282  template <typename T>
283  T get(std::string const& key, T const& default_value) const;
284 
285  //! Convert back to datatools::properties
286  operator datatools::properties() const;
287 
288  // - Inserters
289  //! Insert key-value pair in property_set
290  /*!
291  * Copies of the key and value are stored
292  * \tparam T type of value to store
293  * \param[in] key key to store value at
294  * \param[in] value value to store
295  * \throws wrong_type_error if value's type is not storable
296  * \throws existing_key_error if key is already stored
297  */
298  template <typename T>
299  void put(std::string const& key, T const& value);
300 
301  //! Insert key-value pair in property_set, replacing value if key exists
302  /*!
303  * Copies of the key and value are stored. For existing keys the value
304  * will be overwritten, and the types of the old and new value do not need
305  * to match.
306  * \tparam T type of value to store
307  * \param[in] key key to store value at
308  * \param[in] value value to store
309  * \throws wrong_type_error if value's type is not storable
310  */
311  template <typename T>
312  void put_or_replace(std::string const& key, T const& value);
313 
314  // - Deleters:
315  //! Erase the name-value pair stored at key
316  /*!
317  * \param[in] key key for pair to erase
318  * \returns true if the exists and was erased, false if the key does not exist
319  */
320  bool erase(std::string const& key);
321 
322  private:
323  //! List of types that property_set can hold
324  using types_ =
325  boost::mpl::vector<int, double, bool, std::string, falaise::config::path,
326  falaise::config::quantity, std::vector<int>, std::vector<double>,
327  std::vector<bool>, std::vector<std::string>>;
328  template <typename T>
329  struct can_hold_ {
330  typedef typename boost::mpl::contains<types_, T>::type type;
331  };
332 
333  template <typename T>
334  struct can_hold_<falaise::config::quantity_t<T>> {
335  typedef std::true_type type;
336  };
337 
338  //! Compile-time check that T can be held
339  /*
340  * can_hold_t_<T>::value : true if T can be held, false otherwise
341  */
342  template <typename T>
343  using can_hold_t_ = typename can_hold_<T>::type;
344 
345  //! Return true if value held at key has type T
346  /*
347  * Assert that T be a holdable type before dispatching to the
348  * implementation function checking the specific type
349  */
350  template <typename T>
351  bool is_type_(std::string const& key) const;
352 
353  //! Return true if value at key is an int
354  bool is_type_impl_(std::string const& key, int) const;
355 
356  //! Return true if value at key is a dimensionless double
357  bool is_type_impl_(std::string const& key, double) const;
358 
359  //! Return true if value at key is a bool
360  bool is_type_impl_(std::string const& key, bool) const;
361 
362  //! Return true if value at key is a non-path std::string
363  bool is_type_impl_(std::string const& key, std::string) const;
364 
365  //! Return true if value at key is a falaise::path
366  bool is_type_impl_(std::string const& key, falaise::config::path) const;
367 
368  //! Return true if value at key is a falaise::quantity
369  bool is_type_impl_(std::string const& key, falaise::config::quantity) const;
370 
371  //! Return true if value at key is a std::vector<int>
372  bool is_type_impl_(std::string const& key, std::vector<int>) const;
373 
374  //! Return true if value at key is a std::vector<double> (dimensionless
375  //! doubles)
376  bool is_type_impl_(std::string const& key, std::vector<double>) const;
377 
378  //! Return true if value at key is a std::vector<bool>
379  bool is_type_impl_(std::string const& key, std::vector<bool>) const;
380 
381  //! Return true if value at key is a std::vector<std::string>
382  bool is_type_impl_(std::string const& key, std::vector<std::string>) const;
383 
384  //! Insert key-value pair
385  /*!
386  * Dispatches to specializations or overloads for T
387  */
388  template <typename T>
389  void put_impl_(std::string const& key, T const& value);
390 
391  //! Set result to value held at key
392  /*!
393  * Dispatches to specializations or overloads for T
394  */
395  template <typename T>
396  void get_impl_(std::string const& key, T& result) const;
397 
398  //! Overload of put_impl_ for @ref falaise::config::quantity_t
399  template <typename T>
400  void put_impl_(std::string const& key, falaise::config::quantity_t<T> const& value);
401 
402  //! Overload of get_impl_ for @ref falaise::config::quantity_t
403  template <typename T>
404  void get_impl_(std::string const& key, falaise::config::quantity_t<T>& result) const;
405 
406  datatools::properties ps_; //< underlying set of properties
407 };
408 } // namespace config
409 } // namespace falaise
410 
411 // - Definitions for template functions
412 namespace falaise {
413 namespace config {
414 template <typename T>
415 T property_set::get(std::string const& key) const {
416  if (!ps_.has_key(key)) {
417  throw missing_key_error("property_set does not hold a key '" + key + "'");
418  }
419  if (!is_type_<T>(key)) {
420  throw wrong_type_error("value at '" + key + "' is not of requested type");
421  }
422 
423  T result;
424  get_impl_(key, result);
425  return result;
426 }
427 
428 // Specialization of get for @ref property_set
429 template <>
430 inline property_set property_set::get(std::string const& key) const {
431  if (!is_key_to_property_set(key)) {
432  throw wrong_type_error("key '" + key + "' is not a pure property_set");
433  }
435  ps_.export_and_rename_starting_with(tmp, key + ".", "");
436  return property_set{tmp};
437 }
438 
439 template <typename T>
440 T property_set::get(std::string const& key, T const& default_value) const {
441  T result{default_value};
442  if (ps_.has_key(key)) {
443  if (!is_type_<T>(key)) {
444  throw wrong_type_error("value at '" + key + "' is not of requested type");
445  }
446  get_impl_(key, result);
447  }
448  return result;
449 }
450 
451 // Specialization of get for @ref property_set
452 template <>
453 inline property_set property_set::get(std::string const& key, property_set const& default_value) const {
454  if (!is_key_to_property_set(key)) {
455  return default_value;
456  }
457  return get<property_set>(key);
458 }
459 
460 template <typename T>
461 void property_set::put(std::string const& key, T const& value) {
462  static_assert(can_hold_t_<T>::value, "property_set cannot hold values of type T");
463  // Check directly to use our clearer exception type
464  if (ps_.has_key(key)) {
465  throw existing_key_error{"property_set already contains key " + key};
466  }
467  put_impl_(key, value);
468 }
469 
470 // Specialization of put for @ref property_set
471 template <>
472 inline void property_set::put(std::string const& key, property_set const& value) {
473  // Check both key/table existence
474  if (ps_.has_key(key) || is_key_to_property_set(key)) {
475  throw existing_key_error{"property_set already contains key " + key};
476  }
477  // Have to resort to the low level interface to put data in
478  for (auto& subkey : (value.ps_).keys()) {
479  ps_.store(key+"."+subkey, (value.ps_).get(subkey));
480  }
481 }
482 
483 template <typename T>
484 void property_set::put_or_replace(std::string const& key, T const& value) {
485  // Cannot change type of existing data, so must erase/re-store
486  erase(key);
487  put(key, value);
488 }
489 
490 // Specialization of put_or_replace for @ref property_set
491 template <>
492 inline void property_set::put_or_replace(std::string const& key, property_set const& value) {
493  // Cannot change type of existing data, so must erase/re-store all keys/subkeys
494  erase(key);
495  ps_.erase_all_starting_with(key + ".");
496  put(key, value);
497 }
498 
499 template <typename T>
500 bool property_set::is_type_(std::string const& key) const {
501  static_assert(can_hold_t_<T>::value, "property_set cannot hold values of type T");
502  if (ps_.has_key(key)) {
503  return is_type_impl_(key, T{});
504  }
505  // Absence of key is false (clearly cannot be T)
506  return false;
507 }
508 
509 // Generic put_impl_
510 template <typename T>
511 void property_set::put_impl_(std::string const& key, T const& value) {
512  ps_.store(key, value);
513 }
514 
515 //! Private specialization of put_impl_ for @ref path
516 template <>
517 inline void property_set::put_impl_(std::string const& key, falaise::config::path const& value) {
518  ps_.store_path(key, value);
519 }
520 
521 //! Private specialization of put_impl_ for @ref quantity
522 template <>
523 inline void property_set::put_impl_(std::string const& key,
524  falaise::config::quantity const& value) {
525  ps_.store_with_explicit_unit(key, value());
526  ps_.set_unit_symbol(key, value.unit());
527 }
528 
529 //! Private overload of put_impl_ for @ref quantity_t
530 template <typename T>
531 void property_set::put_impl_(std::string const& key, falaise::config::quantity_t<T> const& value) {
532  ps_.store_with_explicit_unit(key, value());
533  ps_.set_unit_symbol(key, value.unit());
534 }
535 
536 // Generic get_impl_
537 template <typename T>
538 void property_set::get_impl_(std::string const& key, T& result) const {
539  ps_.fetch(key, result);
540 }
541 
542 // Private specialization of get_impl_ for @ref path type
543 template <>
544 inline void property_set::get_impl_(std::string const& key, falaise::config::path& result) const {
545  result = falaise::config::path{ps_.fetch_path(key)};
546 }
547 
548 // Private specialization of get_impl_ for @ref units::quantity type
549 template <>
550 inline void property_set::get_impl_(std::string const& key,
551  falaise::config::quantity& result) const {
552  // Fetch with explicit unit gives value in CLHEP scale, so must
553  // divide by the scaling factor for the symbol
554  double rescaledValue =
556  result = {rescaledValue, ps_.get_unit_symbol(key)};
557 }
558 
559 // Overload for explicitly dimensioned quantities
560 template <typename T>
561 void property_set::get_impl_(std::string const& key, falaise::config::quantity_t<T>& result) const {
562  // fetch with explicit unit gives value in CLHEP scale, so must
563  // divide it by the scaling factor for the symbol
564  double rescaledValue =
566  result = {rescaledValue, ps_.get_unit_symbol(key)};
567 }
568 
569 //! Construct a property_set from an input datatools::properties file
570 /*!
571  * \param filename File from which to read data
572  * \param ps property_set to fill with data
573  */
574 void make_property_set(const std::string& filename, property_set& ps);
575 
576 } // namespace config
577 } // namespace falaise
578 
579 #endif // FALAISE_PROPERTY_SET_H
bool is_empty() const
Returns true if no key-value pairs are held.
bool has_key(std::string const &key) const
Returns true if the property_set stores a pair with the supplied key.
T get(std::string const &key) const
Return the value of type T held at supplied key.
Definition: property_set.h:415
std::string fetch_path(const std::string &name_, int index_=0) const
bool is_key_to_property(std::string const &key) const
Returns true if the key's value is a property/atom.
Definition: property_set.h:28
Exception thrown when requesting a key that is not in the property_set.
Definition: property_set.h:17
void fetch(const std::string &key_, bool &value_, int index_=0) const
void put_or_replace(std::string const &key, T const &value)
Insert key-value pair in property_set, replacing value if key exists.
Definition: property_set.h:484
const std::string & get_unit_symbol(const std::string &prop_key_) const
double fetch_real_with_explicit_unit(const std::string &name_, int index_=0) const
void set_unit_symbol(const std::string &prop_key_, const std::string &unit_symbol="")
Class representing a value and physical unit.
Definition: quantity.h:90
std::string const & unit() const
Return datatools::units tag for the quantity's unit.
Definition: quantity.h:149
void make_property_set(const std::string &filename, property_set &ps)
Construct a property_set from an input datatools::properties file.
void erase_all_starting_with(const std::string &prefix_)
Class representing a filesystem path as held by a property_set.
Definition: path.h:62
bool is_key_to_sequence(std::string const &key) const
Returns true if the keys's value is sequence.
void store(const std::string &key_, const data &value_)
bool has_key(const std::string &prop_key_) const
Definition: metadata_utils.h:35
std::string to_string() const
Returns a string representation of the property_set.
double get_unit(const std::string &unit_str_, bool throw_=false)
void export_and_rename_starting_with(properties &props_, const std::string &prop_key_prefix_, const std::string &new_prefix_) const
bool is_key_to_property_set(std::string const &key) const
Returns true if the key's value is a property_set.
void store_path(const std::string &prop_key_, const std::string &path_value_, const std::string &desc_="", bool lock_=false)
Exception thrown when trying to put to a key already in the property_set.
Definition: property_set.h:22
Template class for a physical value with a strict dimension.
Definition: quantity.h:185
Class holding a set of key-value properties.
Definition: property_set.h:189
void store_with_explicit_unit(const std::string &prop_key_, double value_, const std::string &desc="", bool lock_=false)
void put(std::string const &key, T const &value)
Insert key-value pair in property_set.
Definition: property_set.h:461
property_set()=default
Default constructor.
Types for configuration validation of physical quantities.
bool erase(std::string const &key)
Erase the name-value pair stored at key.
std::vector< std::string > get_names() const
Returns a vector of all keys in the property_set.