aboutsummaryrefslogtreecommitdiff
path: root/src/util/any_map.hpp
blob: d82167d24e337053b2751e95bf80c0f790bcf5aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#pragma once
#include "pre.hpp"

#include <any>
#include <functional>
#include <optional>
#include <typeinfo>
#include <unordered_map>

#include "base.hpp"
#include "format.hpp"

namespace cru::util {
// A map with String as key and any type as value.
// It also has notification when value with specified key changed.
class AnyMap : public Object {
 public:
  using ListenerToken = long;
  using Listener = std::function<void(const std::any&)>;

  AnyMap() = default;
  AnyMap(const AnyMap& other) = delete;
  AnyMap(AnyMap&& other) = delete;
  AnyMap& operator=(const AnyMap& other) = delete;
  AnyMap& operator=(AnyMap&& other) = delete;
  ~AnyMap() override = default;

  // return the value if the value exists and the type of value is T.
  // return a null optional if value doesn't exists.
  // throw std::runtime_error if type is mismatch.
  template <typename T>
  std::optional<T> GetOptionalValue(const String& key) const {
    try {
      const auto find_result = map_.find(key);
      if (find_result != map_.cend()) {
        const auto& value = find_result->second.first;
        if (value.has_value()) return std::any_cast<T>(value);
        return std::nullopt;
      }
      return std::nullopt;
    } catch (const std::bad_any_cast&) {
      throw std::runtime_error(
          Format("Value of key \"{}\" in AnyMap is not of the type {}.",
                 ToUtf8String(key), typeid(T).name()));
    }
  }

  // return the value if the value exists and the type of value is T.
  // throw if value doesn't exists. (different from "GetOptionalValue").
  // throw std::runtime_error if type is mismatch.
  template <typename T>
  T GetValue(const String& key) const {
    const auto optional_value = GetOptionalValue<T>(key);
    if (optional_value.has_value())
      return optional_value.value();
    else
      throw std::runtime_error(
          Format("Key \"{}\" does not exists in AnyMap.", ToUtf8String(key)));
  }

  // Set the value of key, and trigger all related listeners.
  template <typename T>
  void SetValue(const String& key, T&& value) {
    auto& p = map_[key];
    p.first = std::make_any<T>(std::forward<T>(value));
    InvokeListeners(p.second, p.first);
  }

  // Remove the value of the key.
  void ClearValue(const String& key) {
    auto& p = map_[key];
    p.first = std::any{};
    InvokeListeners(p.second, std::any{});
  }

  // Add a listener which is called when value of key is changed.
  // Return a token used to remove the listener.
  ListenerToken RegisterValueChangeListener(const String& key,
                                            const Listener& listener);

  // Remove a listener by token.
  void UnregisterValueChangeListener(ListenerToken token);

 private:
  void InvokeListeners(std::list<ListenerToken>& listener_list,
                       const std::any& value);

 private:
  std::unordered_map<String, std::pair<std::any, std::list<ListenerToken>>>
      map_{};
  std::unordered_map<ListenerToken, Listener> listeners_{};
  ListenerToken current_listener_token_ = 0;
};
}  // namespace cru::util