File.hpp
Go to the documentation of this file.
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 /// \file
5 /// Defines class h5::H5File
6 
7 #pragma once
8 
9 #include <algorithm>
10 #include <exception>
11 #include <hdf5.h>
12 #include <memory>
13 #include <ostream>
14 #include <string>
15 #include <tuple>
16 #include <type_traits>
17 #include <typeinfo>
18 #include <vector>
19 
20 #include "ErrorHandling/Error.hpp"
21 #include "IO/H5/AccessType.hpp"
22 #include "IO/H5/CheckH5.hpp"
23 #include "IO/H5/Header.hpp" // IWYU pragma: keep
24 #include "IO/H5/Object.hpp"
25 #include "IO/H5/OpenGroup.hpp"
26 #include "Utilities/FileSystem.hpp"
27 #include "Utilities/PrettyType.hpp"
28 
29 namespace h5 {
30 /*!
31  * \ingroup HDF5Group
32  * \brief Opens an HDF5 file for access and allows manipulation of data
33  *
34  * Opens an HDF5 file either in ReadOnly or ReadWrite mode depending on the
35  * template parameter `Access_t`. In ReadWrite mode h5::Object's can be inserted
36  * into the file, and objects can be retrieved to have their data manipulated.
37  * Example objects are dat files, text files, and volume data files. A single
38  * H5File can contain many different objects so that the number of files stored
39  * during a simulation is reduced.
40  *
41  * When an h5::object inside an H5File is opened or created the H5File object
42  * holds a copy of the h5::object. Only one object can be open at a time, which
43  * means if a reference to the object is kept around after the H5File's current
44  * object is closed there is a dangling reference.
45  *
46  * \example
47  * To open a file for read-write access:
48  * \snippet Test_H5.cpp h5file_readwrite_file
49  *
50  * \note The dangling reference issue could be fixed by having a function in
51  * addition to `get` that takes a lambda. The lambda takes exactly one parameter
52  * of the type of the h5::Object it will be operating on. While this approach is
53  * likely to be syntactically strange for many users it will most likely be more
54  * performant than the `shared_ptr` solution.
55  *
56  * @tparam Access_t either h5::AccessType::ReadWrite or h5::AccessType::ReadOnly
57  */
58 template <AccessType Access_t>
59 class H5File {
60  public:
61  /*!
62  * \requires `file_name` is a valid path and ends in `.h5`.
63  * \effects On object creation opens the HDF5 file at `file_name`
64  *
65  * @param file_name the path to the file to open or create
66  * @param append_to_file if true allow appending to the file, otherwise abort
67  * the simulation if the file exists
68  */
69  explicit H5File(std::string file_name, bool append_to_file = false);
70 
71  /// \cond HIDDEN_SYMBOLS
72  ~H5File();
73  /// \endcond
74 
75  // @{
76  /*!
77  * \brief It does not make sense to copy an object referring to a file, only
78  * to move it.
79  */
80  H5File(const H5File& /*rhs*/) = delete;
81  H5File& operator=(const H5File& /*rhs*/) = delete;
82  // @}
83 
84  /// \cond HIDDEN_SYMBOLS
85  H5File(H5File&& rhs) noexcept; // NOLINT
86  H5File& operator=(H5File&& rhs) noexcept; // NOLINT
87  /// \endcond
88 
89  /// Get name of the H5 file
90  const std::string& name() const noexcept { return file_name_; }
91 
92  // @{
93  /*!
94  * \requires `ObjectType` is a valid h5::Object derived class, `path`
95  * is a valid path in the HDF5 file
96  * \return a reference to the object inside the HDF5 file.
97  *
98  * @tparam ObjectType the type of the h5::Object to be retrieved, e.g. Dat
99  * @param path the path of the retrieved object
100  * @param args arguments forwarded to the ObjectType constructor
101  */
102  template <
103  typename ObjectType, typename... Args,
104  typename std::enable_if_t<((void)sizeof(ObjectType),
105  Access_t == AccessType::ReadWrite)>* = nullptr>
106  ObjectType& get(const std::string& path, Args&&... args);
107 
108  template <typename ObjectType, typename... Args>
109  const ObjectType& get(const std::string& path, Args&&... args) const;
110  // @}
111 
112  /*!
113  * \brief Insert an object into an H5 file.
114  *
115  * \requires `ObjectType` is a valid h5::Object derived class, `path` is a
116  * valid path in the HDF5 file, and `args` are valid arguments to be forwarded
117  * to the constructor of `ObjectType`.
118  * \effects Creates a new H5 object of type `ObjectType` at the location
119  * `path` in the HDF5 file.
120  *
121  * \return a reference the created object.
122  *
123  * @tparam ObjectType the type of the h5::Object to be inserted, e.g. Dat
124  * @param path the path of the inserted object
125  * @param args additional arguments to be passed to the constructor of the
126  * object
127  */
128  template <typename ObjectType, typename... Args>
129  ObjectType& insert(const std::string& path, Args&&... args);
130 
131  /*!
132  * \brief Inserts an object like `insert` if it does not exist, returns the
133  * object if it does.
134  */
135  template <typename ObjectType, typename... Args>
136  ObjectType& try_insert(const std::string& path, Args&&... args) noexcept;
137 
138  /*!
139  * \effects Closes the current object, if there is none then has no effect
140  */
141  void close_current_object() const noexcept { current_object_ = nullptr; }
142 
143  private:
144  /// \cond HIDDEN_SYMBOLS
145  template <typename ObjectType,
146  std::enable_if_t<((void)sizeof(ObjectType),
147  Access_t == AccessType::ReadWrite)>* = nullptr>
148  ObjectType& convert_to_derived(
149  std::unique_ptr<h5::Object>& current_object); // NOLINT
150  template <typename ObjectType>
151  const ObjectType& convert_to_derived(
152  const std::unique_ptr<h5::Object>& current_object) const;
153 
154  void insert_header();
155 
156  template <typename ObjectType>
158  const std::string& path) const;
159 
160  std::string file_name_;
161  hid_t file_id_{-1};
162  mutable std::unique_ptr<h5::Object> current_object_{nullptr};
163  std::vector<std::string> h5_groups_;
164  /// \endcond HIDDEN_SYMBOLS
165 };
166 
167 // ======================================================================
168 // H5File Definitions
169 // ======================================================================
170 
171 template <AccessType Access_t>
172 template <typename ObjectType, typename... Args,
173  typename std::enable_if_t<((void)sizeof(ObjectType),
174  Access_t == AccessType::ReadWrite)>*>
175 ObjectType& H5File<Access_t>::get(const std::string& path, Args&&... args) {
176  // Ensure we call the const version of the get function to avoid infinite
177  // recursion. The reason this is implemented in this manner is to avoid code
178  // duplication.
179  // clang-tidy: do not use const_cast
180  return const_cast<ObjectType&>( // NOLINT
181  static_cast<H5File<Access_t> const*>(this)->get<ObjectType>(
182  path, std::forward<Args>(args)...));
183 }
184 
185 template <AccessType Access_t>
186 template <typename ObjectType, typename... Args>
187 const ObjectType& H5File<Access_t>::get(const std::string& path,
188  Args&&... args) const {
189  try {
190  current_object_ = nullptr;
191  // C++17: structured bindings
192  auto exists_group_name = check_if_object_exists<ObjectType>(path);
193  hid_t group_id = std::get<1>(exists_group_name).id();
194  if (not std::get<0>(exists_group_name)) {
195  current_object_ = nullptr;
196  CHECK_H5(H5Fclose(file_id_),
197  "Failed to close file: '" << file_name_ << "'");
198  ERROR("Cannot open the object '" << path + ObjectType::extension()
199  << "' because it does not exist.");
200  }
201  current_object_ = std::make_unique<ObjectType>(
202  std::get<0>(exists_group_name),
203  std::move(std::get<1>(exists_group_name)), group_id,
204  std::move(std::get<2>(exists_group_name)), std::forward<Args>(args)...);
205  return dynamic_cast<const ObjectType&>(*current_object_);
206  // LCOV_EXCL_START
207  } catch (const std::bad_cast& e) {
208  current_object_ = nullptr;
209  CHECK_H5(H5Fclose(file_id_),
210  "Failed to close file: '" << file_name_ << "'");
211  ERROR("Failed to cast " << path << " to "
212  << pretty_type::get_name<ObjectType>()
213  << ".\nCast error: " << e.what());
214  } catch (const std::exception& e) {
215  current_object_ = nullptr;
216  CHECK_H5(H5Fclose(file_id_),
217  "Failed to close file: '" << file_name_ << "'");
218  ERROR("Unknown exception caught: " << e.what());
219  // LCOV_EXCL_STOP
220  }
221 }
222 
223 template <AccessType Access_t>
224 template <typename ObjectType, typename... Args>
225 ObjectType& H5File<Access_t>::insert(const std::string& path,
226  Args&&... args) {
227  static_assert(AccessType::ReadWrite == Access_t,
228  "Can only insert into ReadWrite access H5 files.");
229  current_object_ = nullptr;
230  // C++17: structured bindings
231  auto exists_group_name = check_if_object_exists<ObjectType>(path);
232  if (std::get<0>(exists_group_name)) {
233  current_object_ = nullptr;
234  CHECK_H5(H5Fclose(file_id_),
235  "Failed to close file: '" << file_name_ << "'");
236  ERROR(
237  "Cannot insert an Object that already exists. Failed to add Object "
238  "named: "
239  << path);
240  }
241 
242  hid_t group_id = std::get<1>(exists_group_name).id();
243  return convert_to_derived<ObjectType>(
244  current_object_ = std::make_unique<ObjectType>(
245  std::get<0>(exists_group_name),
246  std::move(std::get<1>(exists_group_name)), group_id,
247  std::move(std::get<2>(exists_group_name)),
248  std::forward<Args>(args)...));
249 }
250 
251 template <AccessType Access_t>
252 template <typename ObjectType, typename... Args>
254  Args&&... args) noexcept {
255  static_assert(AccessType::ReadWrite == Access_t,
256  "Can only insert into ReadWrite access H5 files.");
257  current_object_ = nullptr;
258  // C++17: structured bindings
259  auto exists_group_name = check_if_object_exists<ObjectType>(path);
260  hid_t group_id = std::get<1>(exists_group_name).id();
261  return convert_to_derived<ObjectType>(
262  current_object_ = std::make_unique<ObjectType>(
263  std::get<0>(exists_group_name),
264  std::move(std::get<1>(exists_group_name)), group_id,
265  std::move(std::get<2>(exists_group_name)),
266  std::forward<Args>(args)...));
267 }
268 
269 /// \cond HIDDEN_SYMBOLS
270 template <AccessType Access_t>
271 template <typename ObjectType,
272  typename std::enable_if_t<((void)sizeof(ObjectType),
273  Access_t == AccessType::ReadWrite)>*>
275  std::unique_ptr<h5::Object>& current_object) {
276  if (nullptr == current_object) {
277  ERROR("No object to convert."); // LCOV_EXCL_LINE
278  }
279  try {
280  return dynamic_cast<ObjectType&>(*current_object);
281  // LCOV_EXCL_START
282  } catch (const std::bad_cast& e) {
283  ERROR("Failed to cast to object.\nCast error: " << e.what());
284  // LCOV_EXCL_STOP
285  }
286 }
287 template <AccessType Access_t>
288 template <typename ObjectType>
289 const ObjectType& H5File<Access_t>::convert_to_derived(
290  const std::unique_ptr<h5::Object>& current_object) const {
291  if (nullptr == current_object) {
292  ERROR("No object to convert.");
293  }
294  try {
295  return dynamic_cast<const ObjectType&>(*current_object);
296  } catch (const std::bad_cast& e) {
297  ERROR("Failed to cast to object.\nCast error: " << e.what());
298  }
299 }
300 
301 template <AccessType Access_t>
302 template <typename ObjectType>
305  std::string name_only = "/";
306  if (path != "/") {
307  name_only = file_system::get_file_name(path);
308  }
309  const std::string name_with_extension = name_only + ObjectType::extension();
310  detail::OpenGroup group(file_id_, file_system::get_parent_path(path),
311  Access_t);
312 #pragma GCC diagnostic push
313 #pragma GCC diagnostic ignored "-Wold-style-cast"
314  const bool exists =
315  name_with_extension == "/" or
316  H5Lexists(group.id(), name_with_extension.c_str(), H5P_DEFAULT) or
317  H5Aexists(group.id(), name_with_extension.c_str());
318 #pragma GCC diagnostic pop
319  return std::make_tuple(exists, std::move(group), std::move(name_only));
320 }
321 
322 template <>
324  insert<h5::Header>("/header");
325 }
326 // Not tested because it is only required to get code to compile, if statement
327 // in constructor prevents call.
328 template <>
329 inline void H5File<AccessType::ReadOnly>::insert_header() {} // LCOV_EXCL_LINE
330 
331 /// \endcond
332 } // namespace h5
H5File(std::string file_name, bool append_to_file=false)
Definition: File.cpp:20
Defines class OpenGroup for opening groups in HDF5.
Contains functions and classes for manipulating HDF5 files.
Definition: AccessType.cpp:10
ObjectType & insert(const std::string &path, Args &&... args)
Insert an object into an H5 file.
Definition: File.hpp:225
#define ERROR(m)
prints an error message to the standard error stream and aborts the program.
Definition: Error.hpp:35
Defines class h5::Object abstract base class.
Defines class h5::Header.
H5File & operator=(const H5File &)=delete
It does not make sense to copy an object referring to a file, only to move it.
Defines macro CHECK_H5.
std::string get_file_name(const std::string &file_path)
Given a path to a file returns the file name.
Definition: FileSystem.cpp:40
std::string get_parent_path(const std::string &path)
Wraps the dirname function to get the pathname of the parent directory.
Definition: FileSystem.cpp:31
Defines enum for specifying whether the H5 file is ReadWrite or ReadOnly.
void close_current_object() const noexcept
Definition: File.hpp:141
const std::string & name() const noexcept
Get name of the H5 file.
Definition: File.hpp:90
Opens an HDF5 file for access and allows manipulation of data.
Definition: File.hpp:59
Contains a pretty_type library to write types in a "pretty" format.
ObjectType & try_insert(const std::string &path, Args &&... args) noexcept
Inserts an object like insert if it does not exist, returns the object if it does.
Definition: File.hpp:253
Declares functions to do file system manipulations.
Allow read-write access to the file.
ObjectType & get(const std::string &path, Args &&... args)
Definition: File.hpp:175
Defines macro ERROR.
#define CHECK_H5(h5_status, m)
Check if an HDF5 operation was successful.
Definition: CheckH5.hpp:20