openPMD-api
Container.hpp
1 /* Copyright 2017-2021 Fabian Koller and Franz Poeschel
2  *
3  * This file is part of openPMD-api.
4  *
5  * openPMD-api is free software: you can redistribute it and/or modify
6  * it under the terms of of either the GNU General Public License or
7  * the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * openPMD-api is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License and the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * and the GNU Lesser General Public License along with openPMD-api.
19  * If not, see <http://www.gnu.org/licenses/>.
20  */
21 #pragma once
22 
23 #include "openPMD/backend/Attributable.hpp"
24 
25 #include <initializer_list>
26 #include <map>
27 #include <set>
28 #include <stdexcept>
29 #include <string>
30 #include <type_traits>
31 #include <utility>
32 
33 // expose private and protected members for invasive testing
34 #ifndef OPENPMD_protected
35 # define OPENPMD_protected protected
36 #endif
37 
38 
39 namespace openPMD
40 {
41 namespace traits
42 {
49  template< typename U >
50  struct GenerationPolicy
51  {
52  template< typename T >
53  void operator()(T &)
54  {
55  }
56  };
57 } // traits
58 
59 namespace internal
60 {
61 class SeriesData;
62 }
63 
64 namespace detail
65 {
66 /*
67  * This converts the key (first parameter) to its string name within the
68  * openPMD hierarchy.
69  * If the key is found to be equal to RecordComponent::SCALAR, the parentKey
70  * will be returned, adding RecordComponent::SCALAR to its back.
71  * Reason: Scalar record components do not link their containing record as
72  * parent, but rather the parent's parent, so the own key within the "apparent"
73  * parent must be given as two steps.
74  */
75 template< typename T >
76 std::vector< std::string >
77 keyAsString( T && key, std::vector< std::string > const & parentKey )
78 {
79  ( void )parentKey;
80  return { std::to_string( std::forward< T >( key ) ) };
81 }
82 
83 // moved to a *.cpp file so we don't need to include RecordComponent.hpp here
84 template<>
85 std::vector< std::string > keyAsString< std::string const & >(
86  std::string const & key, std::vector< std::string > const & parentKey );
87 
88 template<>
89 std::vector< std::string > keyAsString< std::string >(
90  std::string && key, std::vector< std::string > const & parentKey );
91 }
92 
101 template<
102  typename T,
103  typename T_key = std::string,
104  typename T_container = std::map< T_key, T >
105 >
107 {
108  static_assert(
109  std::is_base_of< AttributableInterface, T >::value,
110  "Type of container element must be derived from Writable");
111 
112  friend class Iteration;
113  friend class ParticleSpecies;
114  friend class internal::SeriesData;
115  friend class SeriesInterface;
116  friend class Series;
117 
118 protected:
119  using InternalContainer = T_container;
120 
121 public:
122  using key_type = typename InternalContainer::key_type;
123  using mapped_type = typename InternalContainer::mapped_type;
124  using value_type = typename InternalContainer::value_type;
125  using size_type = typename InternalContainer::size_type;
126  using difference_type = typename InternalContainer::difference_type;
127  using allocator_type = typename InternalContainer::allocator_type;
128  using reference = typename InternalContainer::reference;
129  using const_reference = typename InternalContainer::const_reference;
130  using pointer = typename InternalContainer::pointer;
131  using const_pointer = typename InternalContainer::const_pointer;
132  using iterator = typename InternalContainer::iterator;
133  using const_iterator = typename InternalContainer::const_iterator;
134 
135  Container(Container const&) = default;
136  virtual ~Container() = default;
137 
138  iterator begin() noexcept { return m_container->begin(); }
139  const_iterator begin() const noexcept { return m_container->begin(); }
140  const_iterator cbegin() const noexcept { return m_container->cbegin(); }
141 
142  iterator end() noexcept { return m_container->end(); }
143  const_iterator end() const noexcept { return m_container->end(); }
144  const_iterator cend() const noexcept { return m_container->cend(); }
145 
146  bool empty() const noexcept { return m_container->empty(); }
147 
148  size_type size() const noexcept { return m_container->size(); }
149 
155  void clear()
156  {
157  if(Access::READ_ONLY == IOHandler()->m_frontendAccess )
158  throw std::runtime_error("Can not clear a container in a read-only Series.");
159 
160  clear_unchecked();
161  }
162 
163  std::pair< iterator, bool > insert(value_type const& value) { return m_container->insert(value); }
164  template< class P >
165  std::pair< iterator, bool > insert(P&& value) { return m_container->insert(value); }
166  iterator insert(const_iterator hint, value_type const& value) { return m_container->insert(hint, value); }
167  template< class P >
168  iterator insert(const_iterator hint, P&& value) { return m_container->insert(hint, value); }
169  template< class InputIt >
170  void insert(InputIt first, InputIt last) { m_container->insert(first, last); }
171  void insert(std::initializer_list< value_type > ilist) { m_container->insert(ilist); }
172 
173  void swap(Container & other) { m_container->swap(other.m_container); }
174 
175  mapped_type& at(key_type const& key) { return m_container->at(key); }
176  mapped_type const& at(key_type const& key) const { return m_container->at(key); }
177 
184  virtual mapped_type& operator[](key_type const& key)
185  {
186  auto it = m_container->find(key);
187  if( it != m_container->end() )
188  return it->second;
189  else
190  {
191  if(Access::READ_ONLY == IOHandler()->m_frontendAccess )
192  {
193  auxiliary::OutOfRangeMsg const out_of_range_msg;
194  throw std::out_of_range(out_of_range_msg(key));
195  }
196 
197  T t = T();
198  t.linkHierarchy(writable());
199  auto& ret = m_container->insert({key, std::move(t)}).first->second;
200  ret.writable().ownKeyWithinParent =
201  detail::keyAsString( key, writable().ownKeyWithinParent );
203  gen(ret);
204  return ret;
205  }
206  }
213  virtual mapped_type& operator[](key_type&& key)
214  {
215  auto it = m_container->find(key);
216  if( it != m_container->end() )
217  return it->second;
218  else
219  {
220  if(Access::READ_ONLY == IOHandler()->m_frontendAccess )
221  {
222  auxiliary::OutOfRangeMsg out_of_range_msg;
223  throw std::out_of_range(out_of_range_msg(key));
224  }
225 
226  T t = T();
227  t.linkHierarchy(writable());
228  auto& ret = m_container->insert({key, std::move(t)}).first->second;
229  ret.writable().ownKeyWithinParent = detail::keyAsString(
230  std::move( key ), writable().ownKeyWithinParent );
232  gen( ret );
233  return ret;
234  }
235  }
236 
237  iterator find(key_type const& key) { return m_container->find(key); }
238  const_iterator find(key_type const& key) const { return m_container->find(key); }
239 
245  size_type count(key_type const& key) const { return m_container->count(key); }
246 
252  bool contains(key_type const& key) const { return m_container->find(key) != m_container->end(); }
253 
261  virtual size_type erase(key_type const& key)
262  {
263  if(Access::READ_ONLY == IOHandler()->m_frontendAccess )
264  throw std::runtime_error("Can not erase from a container in a read-only Series.");
265 
266  auto res = m_container->find(key);
267  if( res != m_container->end() && res->second.written() )
268  {
270  pDelete.path = ".";
271  IOHandler()->enqueue(IOTask(&res->second, pDelete));
272  IOHandler()->flush();
273  }
274  return m_container->erase(key);
275  }
276 
278  virtual iterator erase(iterator res)
279  {
280  if(Access::READ_ONLY == IOHandler()->m_frontendAccess )
281  throw std::runtime_error("Can not erase from a container in a read-only Series.");
282 
283  if( res != m_container->end() && res->second.written() )
284  {
286  pDelete.path = ".";
287  IOHandler()->enqueue(IOTask(&res->second, pDelete));
288  IOHandler()->flush();
289  }
290  return m_container->erase(res);
291  }
293  // virtual iterator erase(const_iterator first, const_iterator last)
294 
295  template <class... Args>
296  auto emplace(Args&&... args)
297  -> decltype(InternalContainer().emplace(std::forward<Args>(args)...))
298  {
299  return m_container->emplace(std::forward<Args>(args)...);
300  }
301 
302 OPENPMD_protected:
303  Container()
304  : m_container{std::make_shared< InternalContainer >()}
305  { }
306 
307  void clear_unchecked()
308  {
309  if( written() )
310  throw std::runtime_error("Clearing a written container not (yet) implemented.");
311 
312  m_container->clear();
313  }
314 
315  virtual void flush(std::string const& path)
316  {
317  if( !written() )
318  {
319  Parameter< Operation::CREATE_PATH > pCreate;
320  pCreate.path = path;
321  IOHandler()->enqueue(IOTask(this, pCreate));
322  }
323 
324  flushAttributes();
325  }
326 
327  std::shared_ptr< InternalContainer > m_container;
328 
338  {
339  std::set< key_type > m_accessedKeys;
340  /*
341  * Note: Putting a copy here leads to weird bugs due to destructors
342  * being called too eagerly upon destruction.
343  * Should be avoidable by extending the frontend redesign to the
344  * Container class template
345  * (https://github.com/openPMD/openPMD-api/pull/886)
346  */
347  Container & m_originalContainer;
348 
349  public:
350  explicit EraseStaleEntries( Container & container_in )
351  : m_originalContainer( container_in )
352  {
353  }
354 
355  template< typename K >
356  mapped_type & operator[]( K && k )
357  {
358  m_accessedKeys.insert( k ); // copy
359  return m_originalContainer[ std::forward< K >( k ) ];
360  }
361 
362  template< typename K >
363  mapped_type & at( K && k )
364  {
365  m_accessedKeys.insert( k ); // copy
366  return m_originalContainer.at( std::forward< K >( k ) );
367  }
368 
370  {
371  auto & map = *m_originalContainer.m_container;
372  using iterator_t = typename InternalContainer::const_iterator;
373  std::vector< iterator_t > deleteMe;
374  deleteMe.reserve( map.size() - m_accessedKeys.size() );
375  for( iterator_t it = map.begin(); it != map.end(); ++it )
376  {
377  auto lookup = m_accessedKeys.find( it->first );
378  if( lookup == m_accessedKeys.end() )
379  {
380  deleteMe.push_back( it );
381  }
382  }
383  for( auto & it : deleteMe )
384  {
385  map.erase( it );
386  }
387  }
388  };
389 
390  EraseStaleEntries eraseStaleEntries()
391  {
392  return EraseStaleEntries( *this );
393  }
394 };
395 
396 } // openPMD
openPMD::Container::erase
virtual iterator erase(iterator res)
Definition: Container.hpp:278
openPMD::Container::EraseStaleEntries
This class wraps a Container and forwards operator[]() and at() to it.
Definition: Container.hpp:337
openPMD::Access::READ_ONLY
@ READ_ONLY
open series as read-only, fails if series is not found
openPMD::ParticleSpecies
Definition: ParticleSpecies.hpp:34
openPMD::Container::contains
bool contains(key_type const &key) const
Checks if there is an element with a key equivalent to an exiting key in the container.
Definition: Container.hpp:252
openPMD::Container::erase
virtual size_type erase(key_type const &key)
Remove a single element from the container and (if written) from disk.
Definition: Container.hpp:261
openPMD::LegacyAttributable
Definition: Attributable.hpp:401
openPMD::Parameter< Operation::DELETE_PATH >
Definition: IOTask.hpp:233
openPMD::UnitDimension::T
@ T
time
openPMD::Container::clear
void clear()
Remove all objects from the container and (if written) from disk.
Definition: Container.hpp:155
openPMD::Series
Root level of the openPMD hierarchy.
Definition: Series.hpp:478
openPMD::AbstractIOHandler::flush
virtual std::future< void > flush()=0
Process operations in queue according to FIFO.
openPMD::auxiliary::OutOfRangeMsg
Return an error string for read-only access.
Definition: OutOfRangeMsg.hpp:37
openPMD::SeriesInterface
Implementation for the root level of the openPMD hierarchy.
Definition: Series.hpp:112
openPMD::Iteration
Logical compilation of data from one snapshot (e.g.
Definition: Iteration.hpp:39
openPMD::Container::count
size_type count(key_type const &key) const
This returns either 1 if the key is found in the container of 0 if not.
Definition: Container.hpp:245
openPMD
Public definitions of openPMD-api.
Definition: Date.cpp:29
openPMD::Container
Map-like container that enforces openPMD requirements and handles IO.
Definition: Container.hpp:106
openPMD::IOTask
Self-contained description of a single IO operation.
Definition: IOTask.hpp:589
openPMD::traits::GenerationPolicy
Container Element Creation Policy.
Definition: Attributable.hpp:47
openPMD::Container::operator[]
virtual mapped_type & operator[](key_type &&key)
Access the value that is mapped to a key equivalent to key, creating it if such key does not exist al...
Definition: Container.hpp:213
openPMD::Container::emplace
auto emplace(Args &&... args) -> decltype(InternalContainer().emplace(std::forward< Args >(args)...))
Definition: Container.hpp:296
openPMD::AbstractIOHandler::enqueue
virtual void enqueue(IOTask const &iotask)
Add provided task to queue according to FIFO.
Definition: AbstractIOHandler.hpp:119
openPMD::Container::operator[]
virtual mapped_type & operator[](key_type const &key)
Access the value that is mapped to a key equivalent to key, creating it if such key does not exist al...
Definition: Container.hpp:184
openPMD::internal::SeriesData
Data members for Series.
Definition: Series.hpp:67