Reactions  0.1.0
Handling reaction trees and decays
database.hpp
Go to the documentation of this file.
1 
4 #pragma once
5 #include <fstream>
6 #include <ios>
7 #include <limits>
8 #include <tuple>
9 #include <vector>
10 
11 #include "reactions/exceptions.hpp"
12 #include "reactions/fields.hpp"
13 
15 
19  template <class Element, class NameField, class IdField> class database {
20 
21  public:
22  using element_type = Element;
23 
24  virtual ~database() {}
25 
36 
38 
39  switch (m_cache.status()) {
40 
41  case (cache::full):
42 
43  out.insert(out.end(), m_cache.begin(), m_cache.end());
44  return out;
45 
46  default:
47 
48  // open the database to count the number of lines
49  auto file = open_database();
50 
51  auto start = skip_commented_lines(file);
52 
53  std::size_t count = 0;
54  while (file.ignore(element_type::line_size + 1)) // include end-of-line
55  ++count;
56 
57  file.clear(); // we reached the end of the file
58 
59  // go back to the start of the table and read the elements
60  file.seekg(start);
61 
62  out.reserve(count + m_cache.size());
63 
64  for (auto i = 0u; i < count; ++i) {
65  std::string line;
66  std::getline(file, line);
67  out.emplace_back(read_element(line));
68  }
69 
70  if (m_cache.size())
71  out.insert(out.end(), m_cache.begin(), m_cache.end());
72 
73  file.close();
74  }
75 
76  return out;
77  }
78 
80  void clear_cache() { m_cache.clear(); }
81 
84 
85  /* \brief Enable the internal cache.
86  *
87  * All the values in the database will be read an stored in a internal
88  * cache. This will speed-up the code, with the caveat of consuming some
89  * memory.
90  */
91  void enable_cache() {
92 
93  if (m_cache.status() == cache::full)
94  return;
95 
96  // open the database to count the number of lines
97  auto file = open_database();
98 
99  auto start = skip_commented_lines(file);
100 
101  std::size_t count = 0;
102  while (file.ignore(element_type::line_size + 1)) // include end-of-line
103  ++count;
104 
105  file.clear(); // we reached the end of the file
106 
107  // go back to the start of the table and read the elements
108  file.seekg(start);
109 
110  std::string line;
112  [this, &file, &line]() -> element_type {
113  std::getline(file, line);
114  return read_element(line);
115  });
116 
117  file.close();
118  }
119 
121  std::string const &get_database_path() const { return m_db; }
122 
129  template <class... Args> void register_element(Args &&... args) {
130 
131  element_type new_element{std::forward<Args>(args)...};
132 
133  // If the cache is enabled, the checks are done within the cache object,
134  // otherwise we must check the database
135  if (m_cache.status() != cache::full) {
136 
137  auto file = open_database();
138 
139  skip_commented_lines(file);
140 
141  std::string line;
142 
143  while (std::getline(file, line)) {
144 
145  typename NameField::value_type name;
146  if (reactions::fields::read_field<typename NameField::range_type>(
147  name, line) == reactions::fields::failed)
149  "Error reading the database; data format not understood");
150 
151  if (new_element.template get<NameField>() == name)
153  "Attempt to register an element with similar name to an "
154  "element in the database");
155 
156  typename IdField::value_type id;
157  if (reactions::fields::read_field<typename IdField::range_type>(
158  id, line) == reactions::fields::failed)
160  "Error reading the database; data format not understood");
161 
162  if (new_element.template get<IdField>() == id)
164  "Attempt to register an element with similar ID to an "
165  "element in the database");
166  }
167  }
168 
169  // this must be done after the checks are done to prevent leaving
170  // the cache in an invalid state
171  m_cache.add_user_element(std::move(new_element));
172  }
173 
174  /* \brief Set the path to the database file.
175  *
176  * If the cache is enabled, reloads the content using the new path.
177  */
179  m_db = s;
180  if (m_cache.status() == cache::full) {
181  disable_cache();
182  enable_cache();
183  }
184  }
185 
187  virtual element_type operator()(int id) const = 0;
188 
190  virtual element_type operator()(std::string const &str) const = 0;
191 
192  protected:
195 
197  class cache {
198 
199  public:
202 
204  using const_iterator_type = typename cache_type::const_iterator;
205  using size_type = typename cache_type::size_type;
206 
207  cache() = default;
208 
210  void clear() {
211  m_vector.clear();
213  m_separator = 0;
214  }
215 
220  m_separator = 0;
221  }
222 
225  if (m_vector.size()) {
226  if (database_size() == 0)
227  return cache_status::user;
228  else
229  return cache_status::full;
230  } else
231  return cache_status::empty;
232  }
233 
235  cache_type const &elements() const { return m_vector; }
236 
238  cache_type &elements() { return m_vector; }
239 
241  const_iterator_type begin() const { return m_vector.cbegin(); }
242 
244  const_iterator_type end() const { return m_vector.cend(); }
245 
248 
251  return m_vector.cbegin() + m_separator;
252  }
253 
256  return database_cend();
257  }
258 
261  return m_vector.cend();
262  }
263 
265  size_type database_size() const { return m_separator; }
266 
269  return m_vector.size() - m_separator;
270  }
271 
273  size_type size() const { return m_vector.size(); }
274 
277  template <class ElementReader>
278  void add_database_elements(size_type n, ElementReader func) {
279 
280  cache_type new_cache;
281  new_cache.reserve(n + user_registered_size());
282 
283  for (auto i = 0u; i < n; ++i) {
284 
285  auto new_element = func();
286 
287  // check that we do not repeat any entry
288  if (user_registered_size() != 0) {
289  auto cend = user_registered_cend();
291  [&new_element](element_type const &el) {
292  return (
293  el.template get<NameField>() ==
294  new_element.template get<NameField>() ||
295  el.template get<IdField>() ==
296  new_element.template get<IdField>());
297  }) != cend)
299  (std::string{"User-defined element clashes with database "
300  "element: \""} +
301  new_cache.back().name() + "\"")
302  .c_str());
303  }
304 
305  new_cache.emplace_back(std::move(new_element));
306  }
307 
308  // insert the elements and assign the separator to the number of
309  // database elements
310  new_cache.insert(new_cache.end(),
313  m_separator = n;
314  m_vector = std::move(new_cache);
315  }
316 
318  template <class... Args>
319  element_type const &add_user_element(Args &&... args) {
320  element_type new_element{std::forward<Args>(args)...};
321  auto e = end();
322  if (std::find_if(begin(), e,
323  [this, &new_element](element_type const &el) {
324  return (el.template get<NameField>() ==
325  new_element.template get<NameField>() ||
326  el.template get<IdField>() ==
327  new_element.template get<IdField>());
328  }) != e) {
330  (std::string{"User-registered element clashes: \""} +
331  new_element.name() + "\"")
332  .c_str());
333  }
334  m_vector.emplace_back(std::move(new_element));
335  return m_vector.back();
336  }
337 
338  protected:
341 
345 
346  } m_cache;
347 
351 
352  if (m_db.empty())
353  throw reactions::database_error("The database has not been specified");
354 
355  std::ifstream file;
356 
357  try {
358  file.open(m_db);
359  } catch (...) {
360  throw reactions::database_error("Unable to access the database");
361  }
362 
363  if (!file.is_open())
364  throw reactions::database_error("Unable to access the database");
365 
366  return file;
367  }
368 
371 
372  char c;
373  while (true) {
374  file.get(c);
375  if (c == '*')
377  else
378  break;
379  }
380  file.unget();
381 
382  return file.tellg();
383  }
384 
386  template <std::size_t I>
387  bool read_field(typename element_type::base_type &tuple,
388  std::string const &line) const {
389 
390  using field = std::tuple_element_t<I, typename element_type::fields_type>;
391 
392  if constexpr (fields::is_optional_field_v<field>) {
393 
394  typename field::value_type::value_type value;
395 
396  auto sc = reactions::fields::read_field<typename field::range_type>(
397  value, line);
398 
400  std::get<I>(tuple).emplace(value);
401 
403  } else
404  return reactions::fields::read_field<typename field::range_type>(
405  std::get<I>(tuple), line) !=
407  }
408 
410  template <std::size_t... I>
411  bool read_line(typename element_type::base_type &tuple,
412  std::string const &line, std::index_sequence<I...>) const {
413  return (read_field<I>(tuple, line) && ...);
414  }
415 
417  bool read_line(typename element_type::base_type &tuple,
418  std::string const &line) const {
419  return read_line(
420  tuple, line,
422  }
423 
425  element_type read_element(std::string const &line) const {
426 
427  typename element_type::base_type tuple;
428 
429  if (!read_line(tuple, line))
431  "Error reading the database; data format not understood");
432 
433  return tuple;
434  }
435 
437  template <class Field, class T> element_type access(T const &v) const {
438 
439  switch (m_cache.status()) {
440  case (cache::full): // the full database is loaded
441 
442  for (auto const &el : m_cache)
443  if (el.template get<Field>() == v)
444  return el;
445 
446  break; // throws an exception
447 
448  case (cache::user): // only user-registered entries exist
449 
450  for (auto const &el : m_cache)
451  if (el.template get<Field>() == v)
452  return el;
453 
454  [[fallthrough]]; // continue as if we had no cache
455 
456  case (cache::empty): // the cache is empty
457 
458  auto file = open_database();
459 
460  skip_commented_lines(file);
461 
462  std::string line;
463 
464  while (std::getline(file, line)) {
465 
466  typename Field::value_type ref;
467  auto sc = reactions::fields::read_field<typename Field::range_type>(
468  ref, line);
469 
470  if (sc == reactions::fields::failed)
472  "Error reading the database; data format not understood");
473 
474  if (ref == v) {
475  element_type el = read_element(line);
476  file.close();
477  return el;
478  }
479  }
480 
481  file.close();
482  }
483 
485  (std::string{"Unable to find element with "} + Field::title + " \"" +
486  fields::to_string(v) + '"')
487  .c_str());
488  }
489 
490  database(std::string &&db) : m_db{std::move(db)} {}
491  database(database &&) = delete;
492  database(database const &) = delete;
493  void operator=(database &&) = delete;
494  void operator=(database const &) = delete;
495  };
496 } // namespace reactions::database
T empty(T... args)
bool read_line(typename element_type::base_type &tuple, std::string const &line) const
Read all the fields from a line.
Definition: database.hpp:417
the conversion succeeded
Definition: fields.hpp:358
T open(T... args)
const_iterator_type begin() const
Begining of the cache.
Definition: database.hpp:241
std::string to_string(T const &v)
Convert the given object to a string.
Definition: fields.hpp:546
Base database class.
Definition: database.hpp:19
virtual element_type operator()(int id) const =0
Create an element accessing by ID.
cache_type & elements()
Underlying vector of elements.
Definition: database.hpp:238
void register_element(Args &&... args)
Register a new element.
Definition: database.hpp:129
T unget(T... args)
Definition: database.hpp:201
cache_status status() const
Status of the cache.
Definition: database.hpp:224
Exceptions that can be thrown when running the functions of the package.
T getline(T... args)
element_type const & add_user_element(Args &&... args)
Add a new element (by the user)
Definition: database.hpp:319
T make_move_iterator(T... args)
the conversion failed
Definition: fields.hpp:360
T end(T... args)
Raised whenever a problem is detected in the database.
Definition: exceptions.hpp:101
typename cache_type::const_iterator const_iterator_type
Definition: database.hpp:204
cache_status
Code to define the status of the cache.
Definition: database.hpp:201
const_iterator_type user_registered_cend() const
End of the user-registered elements.
Definition: database.hpp:260
T shrink_to_fit(T... args)
std::string m_db
Path to the database file.
Definition: database.hpp:194
void operator=(database &&)=delete
STL class.
class reactions::database::database::cache m_cache
bool read_line(typename element_type::base_type &tuple, std::string const &line, std::index_sequence< I... >) const
Read all the fields from a line.
Definition: database.hpp:411
void clear()
Clear the cache.
Definition: database.hpp:210
T erase(T... args)
Raised when an element is not found within a database.
Definition: exceptions.hpp:88
Definition: database.hpp:14
const_iterator_type database_cbegin() const
Begining of the database elements.
Definition: database.hpp:247
const_iterator_type end() const
End of the cache.
Definition: database.hpp:244
void enable_cache()
Definition: database.hpp:91
T clear(T... args)
T tellg(T... args)
virtual ~database()
Definition: database.hpp:24
size_type size() const
Number of cached elements.
Definition: database.hpp:273
T get(T... args)
T insert(T... args)
T find_if(T... args)
T size(T... args)
void set_database_path(std::string const &s)
Definition: database.hpp:178
the object is missing
Definition: fields.hpp:359
Common operations on fields.
T cbegin(T... args)
database(std::string &&db)
Definition: database.hpp:490
std::string const & get_database_path() const
Get the path to the database file.
Definition: database.hpp:121
Definition: database.hpp:201
T back(T... args)
void clear_database_elements()
Clear the cache.
Definition: database.hpp:217
const_iterator_type database_cend() const
End of the database elements.
Definition: database.hpp:250
cache_type const & elements() const
Underlying vector of elements.
Definition: database.hpp:235
Element element_type
Definition: database.hpp:22
typename cache_type::size_type size_type
Definition: database.hpp:205
std::vector< element_type > all_elements() const
All the elements in the database file.
Definition: database.hpp:35
element_type access(T const &v) const
Access an element using the field accessor.
Definition: database.hpp:437
void disable_cache()
Disable the cache.
Definition: database.hpp:83
cache_type m_vector
Collection of elements.
Definition: database.hpp:340
element_type read_element(std::string const &line) const
Advance to the next element in the file and read it.
Definition: database.hpp:425
bool read_field(typename element_type::base_type &tuple, std::string const &line) const
Read a field with the given index from a line.
Definition: database.hpp:387
T is_open(T... args)
std::streampos skip_commented_lines(std::ifstream &file) const
Skip lines with comments (preceeded by *)
Definition: database.hpp:370
void add_database_elements(size_type n, ElementReader func)
Definition: database.hpp:278
const_iterator_type user_registered_cbegin() const
Begining of the user-registered elements.
Definition: database.hpp:255
T ignore(T... args)
size_type database_size() const
Number of elements associated to the database.
Definition: database.hpp:265
std::ifstream open_database() const
Open the database.
Definition: database.hpp:350
STL class.
size_type m_separator
Definition: database.hpp:344
size_type user_registered_size() const
Number of user-registered elements.
Definition: database.hpp:268
T reserve(T... args)
Cache of elements.
Definition: database.hpp:197
T emplace_back(T... args)
void clear_cache()
Clear the cache, removing also user-registered elements.
Definition: database.hpp:80